EVA ICS documentation

EVA ICS is a platform for automated control and monitoring systems development, for any needs, from home/office to industrial setups. It is completely free for non-commercial use as well as for commercial, on condition that enterprise integrates it on its own. The product is distributed as a free software and is available under Apache License 2.0.

Automated control systems are facing a new stage of evolution: IoT-devices become interesting for those, who have never dealt with automation, cheap programmable devices become reliable enough for industrial use, commercial solutions move away from old protocols and involve computer networks instead. We do not reform automation – we change the approach: taking the classical technology as a basis, we simplify everything else to the maximum. Automation is simple and available for everyone!

What is EVA

  • Universal controllers for management and monitoring of all your equipment, on the basis of which you can develop your own automation applications easily and quickly.
  • Notification system, that instantly informs applications on current events.
  • Logic Manager programming logic controllers used for automatic data processing and decision-making.
  • SFA Framework and SFA Templates, allowing quick development of the interfaces for a specific configuration.

EVA can be installed either partially or fully, it can be scaled up to many servers or all components can be installed onto the only one. The system is designed in such a way, that it can work on any hardware: from fat servers to mini-computers with only one smart card in the “read-only” mode.

Architecture of EVA provides a high scalability: one system can support dozens and even hundreds of thousands of devices through processing events via separate subsystems and collecting all data to a unified database.

The latest EVA ICS version is 3.1.1. CHANGELOG

What you get with EVA

  • use pre-made drivers or write simple scripts for your automation hardware and keep them organized, queued and safely executed with Universal Controller
  • easily collect data from the hardware using MQTT or SNMP traps with the built-in SNMP trap handler server
  • collect data from your microcontrollers with a simple UDP API
  • test and monitor the initial setup with controllers’ EI web interfaces
  • exchange all automation data between multiple servers with EVA controllers and your own apps via MQTT server or HTTP notifiers
  • use EVA Logic Manager to write powerful macros which can be run automatically on events in accordance with the decision rules you set up
  • collect everything and control your whole setup with the aggregator controllers
  • API Clients to quickly connect controllers’ API to your apps
  • develop a modern real-time websocket-powered SCADA web applications with SFA Framework
  • and much more

Installation

All you need to install EVA is to download the latest update from https://www.eva-ics.com/, unpack the archive to any folder and everything is almost ready to use.

System Requirements

  • Python version 3 (preferably 3.4+) or later, plus pip3 for automatic installation of the additional modules
  • Linux or UNIX-compatible system
  • For SFA PVT to work with images: libjpeg-dev and libjpeg8-dev (for PIL / pillow installation)
  • realpath (available in all modern Linux distributions)
  • EVA ICS can run on any Linux or UNIX-compatible system, but for the smooth install we recommend Ubuntu or Debian.
  • To sync item status between the components in real time - MQTT-server (i.e. mosquitto)

Warning

Installation scripts try to install all required Python modules automatically, but some of them can have problems installing with pip - install can fail or be slow. It’s better to install these modules manually, before running EVA installation scripts. Currently the problems can be expected with:

  • pandas (python3-pandas)
  • pysnmp (python3-pysnmp4)
  • cryptography (python3-cryptography)

Initial setup

Note

If you are going to run any controllers under restricted user account, make sure it has a valid shell set.

Preparing the system

Install required system packages and heavy Python modules from the OS repository. here is an example how to install them on Debian-based Linux (i.e. Ubuntu):

apt install -y curl python3 python3-pip python3-pandas python3-pysnmp4 python3-cryptography
Installing local MQTT server

If you plan to use local MQTT server, here is an example how to install mosquitto MQTT server on Debian-based Linux (i.e. Ubuntu):

apt install -y mosquitto
# stop mosquitto
/etc/init.d/mosquitto stop
# let the server listen to localhost only
echo "bind_address 127.0.0.1" >> /etc/mosquitto/mosquitto.conf
# start mosquitto back
/etc/init.d/mosquitto start
# make sure mosquitto is running
ps auxw|grep mosquitto

Options for EVA ICS:

  • mqtt host: localhost
  • mqtt port: 1883 (default)
  • mqtt user, password: leave empty
  • mqtt space: leave empty
  • mqtt ssl: leave empty (answer ‘n’ if using easy-setup)
Downloading and extracting EVA ICS distribution

Go to EVA ICS website, download most recent distribution and unpack it e.g. to /opt/eva:

cd /opt
curl https://get.eva-ics.com/3.x.x/stable/eva-3.x.x-xxxxxxxxxx.tgz -o eva.tgz
tar xzvf eva.tgz
mv eva-3.x.x eva
cd eva
Easy setup

Warning

If you want to run some components under restricted users, create var and log folders in EVA installation dir and make sure the restricted users have an access to these folders before running easy-setup. If you’ve customized ini files in etc, make sure the restricted user has an access to both <component>.ini and <component>_apikeys.ini.

If you want to make some initial customization, e.g. name the controllers different from the host name, make changes in etc/uc.ini, etc/lm.ini and etc/sfa.ini configs first.

  • For the interactive setup, run ./easy-setup in EVA folder and follow the instructions.
  • For the automatic setup, run ./easy-setup -h in EVA folder and choose the installation type.

Setup log rotation by placing etc/logrotate.d/eva-* files to /etc/logrotate.d system folder. Correct the paths to EVA files if necessary.

cp ./etc/logrotate.d/eva-* /etc/logrotate.d/

Setup automatic launch at boot time by placing EVADIR/sbin/eva-control start command into system startup e.g. either to /etc/rc.local on System V, or for systems with systemd (all modern Linux distributions):

cp ./etc/systemd/eva-ics.service /etc/systemd/system/
systemctl enable eva-ics
Manual setup
  • Run ./install/install-without-setup in EVA folder
  • In etc folder copy uc.ini-dist into uc.ini; if you plan to use Universal Controller, change necessary configuration parameters.
  • Copy uc_apikeys.ini-dist into uc_apikeys.ini and set the API keys
  • Repeat the procedure for the configuration of Logic Manager and SCADA Final Aggregator
  • In etc folder copy eva_servers-dist into eva_servers, set ENABLED=yes for the chosen controllers, set USER params to run certain controllers under restricted users.
UC_ENABLED=yes
LM_ENABLED=yes
SFA_ENABLED=yes
LM_USER=nobody
SFA_USER=nobody
  • Make sure all restricted users have an access to log, var and runtime/db folders as well to runtime files and folders plus to config files in etc (both <component>.ini and <component>_apikeys.ini).
  • Setup log rotation by placing etc/logrotate.d/eva-* files to /etc/logrotate.d system folder. Correct the paths to EVA files if necessary.
  • Setup automatic launch at boot time by placing EVADIR/sbin/eva-control start command into system startup e.g. to /etc/rc.local or place ./etc/systemd/eva-ics.service to /etc/systemd/system/ for systemd-based startup.
  • Configure the notification system if required.
  • Start EVA:
./sbin/eva-control start

The system is ready. Enable automatic launch in the same way as for easy-setup.

Note

To change or set up (without easy-setup.sh) the user controllers are running under, use ./set-run-under-user.sh script to adjust runtime and database permissions.

Updating

Using EVA Shell
  • Backup everything in system shell
  • Launch EVA Shell (/opt/eva/bin/eva-shell)
  • Backup configuration (type backup save command in EVA Shell)
  • Type update command in EVA Shell
Using system shell
  • Backup everything
  • Run the following command:
curl -s <UPDATE_SCRIPT_URL> | bash /dev/stdin
#i.e.
#curl -s https://get.eva-ics.com/3.1.1/stable/update.sh | bash /dev/stdin
  • If updating from 3.0.2 or below, you may also want to enable controller watchdog (copy etc/watchdog-dist to etc/watchdog and edit the options if required)

Note

The system downgrade is officially not supported and not recommended.

Moving to another folder

EVA ICS doesn’t depend on any system paths, this allows to easy rename or move its folder or clone the installation. Just do the following:

  • stop EVA ICS (./sbin/eva-control stop)
  • rename, move or copy EVA ICS folder
  • if you’ve copied the folder, edit configuration files to make sure components use different ports and/or interfaces
  • start EVA ICS back (./sbin/eva-control start)
  • correct logrotate and on-boot startup paths

Watchdog

Watchdog process is started automatically for each EVA controller and tests it with the specified interval. Controller should respond to API call test within the specified API timeout or it is forcibly restarted.

Watchdog configuration is located in file etc/watchdog and has the following params:

  • WATCHDOG_INTERVAL checking frequency (default: 30 sec)
  • WATCHDOG_MAX_TIMEOUT maximum API timeout (default: 5 sec)
  • WATCHDOG_DUMP if the controller is not responding, try to create crash dump before restarting (default: no).

How to assign IDs to items

All system items including macros have their own ids. Item id should be unique within one server in simple layout. When using enterprise layout, it is possible for items to have the same id in different groups, however full item id (group/id) should be always unique within one controller.

Note

Before adding items, consider what kind of layout you want to use: simple or enterprise

Item groups can coincide and often it is convenient to make them similar: for example, if you set groups=security/# in API key config file, you will allow the key to access all the items in the security group and its subgroups regardless of whether it is macro, sensor or logic variable. To set access to a group of particular items, use oids, e.g. groups=sensor:security/#.

This does not apply to decision rules and macros: a unique id is generated for each rule automatically, macro id should be always unique.

Note

The triple underline (___) is used by system and should not be used in item IDs or groups.

Log file customization

Perform these on the installed Python modules to avoid any extra information in logs:

  • dist-packages/ws4py/websocket.py and dist-packages/ws4py/manager.py - replace all logger.error calls to logger.info

  • dist-packages/urllib3/connectionpool.py - if you set up the controllers to bypass SSL verifications (don’t do this on production!), remove or comment

    if not conn.is_verified:warnings.warn((….

Using NGINX as a frontend for SFA interface

Suppose NGINX operates on 8443 port with SSL, and SCADA Final Aggregator - without SSL. Let’s make the task even more complicated: let NGINX receive the request not directly, but via port forwarding from the router listening on an external domain (i.e. port 35200).

Additionally, we want to authorize:

  • by IP address or
  • basic auth by username/password or
  • by cookie-token (required for EVA Android Client since it passes basic auth only when the server is requested for the first time)

The server should allow access upon the authorization of any type.

Our final config for all of this should look like:

map $cookie_letmein $eva_hascookie {
  "STRONGSECRETRANDOMTOKEN" "yes";
  default           "no";
}

geo $eva_ip_based {
  192.168.1.0/24 "yes"; # our internal network
  default        "no";
}

map $eva_hascookie$eva_ip_based $eva_authentication {
  "yesyes" "off"; # cookie and IP matched - OK
  "yesno"  "off"; # cookie matched, IP did not - OK
  "noyes"  "off"; # cookie did not match, IP did - OK
  default  "?"; # everything else - demand the password
}

upstream eva-sfa {
        server 127.0.0.1:8828;
}

server {
    listen 192.168.1.1:8443;
    server_name  eva;
    ssl                  on;
    ssl_certificate /opt/eva/etc/eva.crt;
    ssl_certificate_key /opt/eva/etc/eva.key;
    ssl_session_timeout  1m;
    ssl_protocols  SSLv3 TLSv1;
    ssl_ciphers  HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers   on;

    # proxy for HTTP
    location / {
        auth_basic $eva_authentication;
        auth_basic_user_file /opt/eva/etc/htpasswd;
        add_header Set-Cookie "letmein=STRONGSECRETRANDOMTOKEN;path=/";
        proxy_buffers 16 16k;
        proxy_buffer_size 16k;
        proxy_busy_buffers_size 240k;
        proxy_pass http://eva-sfa;
        # a few variables for backend, though in fact EVA requires X-Real-IP only
        proxy_set_header X-Host $host;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Frontend "nginx";
        proxy_redirect http://internal.eva.domain/ui/ https://external.eva.domain:35200/ui/;
    }

    # proxy for WebSocket
    location /ws {
        auth_basic $eva_authentication;
        auth_basic_user_file /opt/eva3/etc/htpasswd;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_buffers 16 16k;
        proxy_buffer_size 16k;
        proxy_busy_buffers_size 240k;
        proxy_pass http://eva-sfa;
        proxy_set_header X-Host $host;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Frontend "nginx";
    }
}

EVA ICS CHANGELOG

3.1.1 (2018-10-22)

Common
  • fixes: interactive prompt behavior
  • fixes: API client libs check result of “phi_test” and “phi_exec” functions
  • history for interactive shell mode (to turn off set EVA_CLI_DISABLE_HISTORY=1 system environment variable)
  • New management CLI: eva-shell (interactive by default)
  • Backup/restore operations (with eva-shell)
  • Dynamic API key management via CLI and API
UC
  • fixes: device commands in enterpise layout
  • performance improvements
  • “update” command without params starts item passive update
  • batch commands in UDP API (separated with new line)
  • encryption and authentication in UDP API
  • custom packet handlers in UDP API
  • new API function: “test_controller”, detailed info in “list controllers”
  • MQTT tools for PHIs
  • test-phi CLI tool
LM
  • fixes: double quoted macro arguments in DM rules
  • fixes: gain param in “tts” and “audio” extensions
  • “action_toggle” macro func, “toggle” acts as an alias for unit oids
  • “shared” and “value” macro funcs default return values
  • new API function: “test_controller”, detailed info in “list controllers”
  • new LPI: usp (unit single port)
  • test-ext CLI tool
SFA
  • new API function: “test_controller”, detailed info in “list controllers”
  • SFA framework fixes and improvements

3.1.0 (2018-09-01)

UC drivers, device templates, state history, charts and other new features

Core
  • working with locks now require allow=lock apikey permission
  • new notifier type: db, used to store item state history
  • SYS API functions: notifiers, enable_notifier, disable_notifier. the enable/disable API functions change notifier status only temporary, until the controller is restarted
  • MQTT SSL support
  • JSON notifiers. GET/POST notifiers are marked as deprecated and should not be used any more.
  • exec function (cmd, run) string arguments splitted with ‘ ‘ now support the spaces inside (e.g. ‘this is “third argument”’)
UC
  • new uc-cmd cli
  • old uc-cmd renamed to uc-api
  • UC drivers: logical to physical (LPI) and physical (PHI) interfaces
  • native ModBus support (drivers only)
  • device templates
  • new function “state_history” in UC API
  • EVA_ITEM_OID var in the environment of UC scripts
  • action status label (case insensitive) may be used instead of number, if the label is not defined, API returns 404 error
  • new key permission: “device”, allows calling device management functions.
  • uc-tpl device template validator and generator (alpha)
  • unit and sensor items now have physical location. If location is specified as coordinates (x:y or x:y:z), loc_x, loc_y and loc_z props become available
  • UC EI now should be enabled/disabled in uc.ini
LM PLC
  • new lm-cmd cli
  • old lm-cmd renamed to lm-api
  • macro extensions
  • macro function “unlock” now return false if the lock hasn’t been locked or doesn’t exist
  • unlock macro function may throw an exception if the controller forbids its functionality, in case the controller has no master key defined
  • new functions “state_history” in LM API and “history” (equivalent) in macros
  • new functions: status, value, nstatus, nvalue with oid support
  • new DM rule events: on nstatus, nvalue change (for units)
  • device management functions: “create_device”, “update_device”, “destroy_device”
  • “set_rule_prop” macro function
  • “alias” macro function
  • rule filter in LM EI
  • LM EI now should be enabled/disabled in lm.ini
SFA
  • fixes: rule management functions
  • new sfa-cmd cli
  • sfa-cmd renamed to sfa-api
  • new function “state_history” in SFA API and SFA Framework
  • all functions now accept item oids
  • “result” function returns the result of macro execution if macro action uuid or macro id (in the oid format) specified
  • state API function accepts “full” parameter
  • full SFA states now have item descriptions and status labels (for units)
  • SFA API groups function now accept “g” parameter to filter group list (with MQTT-style wildcards)
  • SFA rpvt function to load documents from remote servers
  • SFA cvars are automatically available in SFA Framework app. Note: SFA cvars are public and may be obtained with any valid API key
  • SFA Framework is now jQuery 3 compatible, included jQuery lib updated to 3.3.1
  • SFA Framework item states now also have description and status labels fields
  • eva_sfa_groups function, returns item groups list (with optional filter)
  • eva_sfa_chart function, displays item state charts
  • eva_sfa_popup function, displays popups and info windows
  • new ws event: server restart and eva_sfa_server_restart_handler in a framework. SFA API function “notify_restart” allows to notify clients about the server restart w/o actual restarting (e.g. when restarting frontend)
  • jinja2 templates for SFA ui and PVT files (all files with .j2 extension are served as templates). index.j2 has more priority than index.html
API Client
  • new API function call result: “result_invalid_params” (11)
Common
  • new notifier management CLI (old CLI tools available in legacy folder)
  • watchdog to test/automatically restart controllers in case of failure
  • oid support in API keys
  • other stability improvements

3.0.2 (2018-06-23)

Bugfix release, some new urgent features, stability improvements

EVA documentation is now available in reStructuredText format and at https://eva-ics.readthedocs.io

Emergency interfaces
  • fixes: correct display of long item names
  • fixes: various bug fixes
  • refresh buttons on item pages
  • LM EI: reset button and expire timer in LM EI show/hide when prop changed
Core
  • fixes: remove empty controller group when all objects are deleted
  • fixes: remote items correctly display state in list_remote
  • fixes: disabled sensors and lvars should not react to expiration
  • each set_prop call now logs what’s actually changed
  • added item oid (type:group/item_id) - reserved for the future releases
  • added stop_on_critical option in config (default: yes), server will be restarted via safe-run if critical exception occur
  • uptime in dump and test API function, last 100 exceptions are now stored in a dump, dumps are now compressed with gzip
  • API functions now support JSON requests
UC
  • action_toggle function to quickly toggle status of simple units
LM PLC
  • list_remote returns array + controller_id proprety instead of dict
  • result function in macro api. terminate and result function accept action uuid as a param
  • on_set lm rule (status changed to 1)
  • new LM API and macro functions: clear (set lvar value to 0), toggle (toggles lvar value between 0 and 1)
  • cmd macro function now accepts full controller id (uc/controller_id) as well as short
  • new macro functions for file management: ls, open_oldest, open_newest
SFA
  • fixes: dm_rule_props acl in SFA
  • list_remote returns array instead of dict + controller_id proprety
  • list_macros contains now controller property
  • append_controller now tries to autodetect controller type if no type is specified
  • sfa pvt access logs
  • reset, toggle, clear, action_toggle, result and terminate by uuid funcs in sfa & sfa framework
  • reload_clients command and sfa framework reload event handler
  • eva_sfa_expires_in function in a framework to work with timers
  • log processing functions in a framework
  • wildcard masks in eva_sfa_state and eva_sfa_register_update_state
Common
  • easy-setup.sh - an interactive/automatic script to quickly set up the current host
  • ability to run controllers under restricted user

3.0.1 (2018-02-21)

Minor release with some urgent features

Core
  • EVA_ITEM_PARENT_GROUP variable in script ENV which contains the parent group of the item
  • cvars now can be set as global or assigned to the specified item group i.e. ‘VAR1’ - global cvar, available to the all scripts, ‘group1/VAR2’ - variable available only to scripts from group ‘group1’ (as ‘VAR2’), ‘group2/VAR2’ - variable available only to group ‘group2’ (also as ‘VAR2’). Used by UC scripts to let one script manage different items
UC
  • ‘update_delay’ prop - item passive update may start with a delay to prevent multiple updates running simultaneously producing high system load
  • ‘clone’ function in UC API and uc-cmd to clone items
  • ‘clone_group’ function - clones all matching items in a group
  • ‘destroy_group’ function destroys all items in the specified group
LM
  • item id in LM rules match by simple mask (i.e. ‘*id’* or ‘id’* or ‘*id’)

3.0.0 (2017-10-19)

First public release

Security

Common recommendations

Traditionally, automation systems and protocols have been designed as not completely secure. As for reliability, you may agree that it would be a pity if the motor or door gets stuck because of incorrect access rights to a file or an expired SSL certificate.

Universal Controller, Logic Manager, SCADA Final Aggregator subsystems, as well as their interfaces, openly exchange API keys. If you do not use SSL, these keys can easily be caught. That is why only a separate secure network should be used to connect the controllers in the working configuration. Alternatively, you should install as many controllers as possible on the same server. Built-in web interfaces of the individual subsystems are called Emergency Interfaces. Therefore, they can only be used during primary setup, testing as well as in case of emergency but not on a regular basis.

All programs and extensions which use API calls should be connected at least via SSL or, ideally, in a separate secure network.

If you use secure networks or separate VLANs, it is not recommended to use SSL for API calls to avoid making setup complex and increasing load on your equipment.

Network design

The recommended enterprise configuration of the system is represented in the following scheme:

secure network design
  • Public network external network (company’s local network); applications working in a public network (usually SCADA interfaces), should be protected from unauthorized access as much as possible: they should use SSL and complex passwords and be regularly updated with the latest security patches.
  • Supervisory network network accessed by authorized company employees only; it includes MQTT (event server), PLC (Logic Manager), custom automation applications and “public” interfaces of Universal Controller subsystems. The security policies are minimal. They are used not as means for protection against hacking, but rather for eliminating the possibility of component malfunction. Passwords/keys can be simple, pure HTTP without SSL can be used in order to reduce the load and avoid system failure because of incorrect certificates.
  • Direct control network network in which only item management controllers operate, e.g. “productional” Universal Controller interfaces, TCP/IP-controlled relays, network sensor controllers etc.

In many setups, you may combine Supervisory network and Direct control network into a single network. As for security, this decision is not that bad, if the primary goal to divide these networks is comfortable maintenance.

Primary EVA interface is itself rather secure. Still, when connecting via insecure networks, especially via external Internet connection, it is highly recommended to:

  • use frontend (NGINX, Apache)
  • use SSL only (if frontend is present - use it for SSL processing)
  • use firewall and forward only one port to the server with an interface

It is strongly recommended to access enterprise configurations with VPN only.

It is not recommended to keep debugging mode enabled in the production system, because some important data may be recorded in the log files.

Should I run it as root?

  • Universal Controller is designed to be run on virtual machines, microcomputers, and embedded systems. If server directly controls connected devices, you should run it as root in order to avoid any device access errors. UC security bottleneck (when working under root) - API and UC EI interface. However, you should use API in Supervisory network only and UC EI interface should be turned off and used only in case of emergency.
  • Logic Manager does not require direct access to the equipment, that is why it can be run as root on the selected system (if really required) or as a restricted user on the common-purpose servers. If Logic Manager API and interface are available only in Supervisory network, this issue is not critical for security.
  • All external interfaces of the system, including SCADA Final Aggregator, should be run only under restricted users and protected with additional frontend and/or firewall.

API recommendations

X-Real-IP HTTP header

HTTP API uses X-Real-IP header variable to determine real IP address of client when working behind frontend. This can be used by attacker to compromise real IP address and bypass hosts_allow/hosts_assign key access control lists. Frontend should always clear X-Real-IP header variable and set it to the real ip of remote client.

X-Real-IP feature is disabled by default. To enable it, set param x_real_ip=yes in webapi section of controller configuration file.

Universal Controller API keys
  • The key should contain at least 14 characters, including numbers, lowercase, and uppercase letters. Default keys generated during easy setup are random sha256 64-byte length hashes, which’s more than enough for security unless they’re transferred between controllers in an insecure network and sniffed.
  • As far as day-to-day tasks are concerned, it is recommended to use API key masterkey (as well as all keys with master rights) only locally or for the system configuration/emergency situations.
  • For the use of UC EI it is recommended to create operator key with groups = #, sysfunc = yes permissions.
  • When connecting to Logic Manager and SCADA Final Aggregator it is recommended to create a separate key with rights for certain item groups, sysfunc = no, optionally allow = cmd.
  • All external applications should have their own keys with restricted access rights to the required functions and items only.
  • After installation, make sure that etc/uc_apikeys.ini file has 0600 permissions (and owned by the user you are running the controller under)
Logic Manager API keys
  • After the initial configuration is complete, it is recommended to connect external applications only via keys with certain rights. Master key should not be used.
  • Sometimes third-party applications need to read/modify the rules of decision-making matrix. Instead of using master key, we recommend creating a key with allow = dm_rule_props permissions, which allows you to change parameters in_range_*, enabled and chillout_time, plus dm_rules_list (for all rules). In case you need to be able to read/change these parameters for a certain rule, specify its ID in items API key parameter.
SCADA Final Aggregator interfaces
  • If interface is available from within a public network, you should always use frontend with additional authentication
  • Private data should be stored on SFA PVT server or protected in other way.
Common API security recommendations

If server is present in several VLANs, make sure that API listens only on Supervisory network address. If you do not use UDP API or SNMP traps in Universal Controller, disable them in the controller configuration. Do not enable the remote file control function unless it’s necessary for external apps.

Developer mode

Every component may be started in a “developer mode”: if enabled, all data, including API keys, is openly written in the log file. That is why we do not advise you to enable it unless you are our developer or integrator. Still, as far as the whole system code is open, you can try to enable it on your own responsibility. Never enable developer mode on the working system and avoid enabling debug mode as well.

If you contacted the product vendor or integrator who explained to you how to make a system “dump”, you should delete it from the system immediately after the file is no longer required. “dump” contains plenty of confidential data, including all API KEYS. Never give dump files to unauthorized persons! This is the same as giving away all configuration files, including the keys.

Dump file should be sent only via secure channels or in an encrypted form.

Notification system

The Notification System is embedded in all EVA subsystems. All the events of these subsystems are sent to the notification servers via objects called “notifiers” which contain the configuration of the notification endpoints.

Event structure

Each event includes the following data:

  • Event subject (not to be confused with MQTT subject)
  • Notification space May be used to divide the controlled structure into sectors, e. g. city1/office1, plant1 etc. By dividing spaces you can separate one EVA installation from another using the same notification server, e. g. to create your own multi-control and multi-monitoring systems.
  • Event data (usually JSON dict with) data on what’s actually happened
Event subjects

There are several event subjects in EVA. Each notification endpoint can be subscribed either to one of them or to several ones.

state - item state change event

The event notifications with the “state” subject are sent by Universal Controller and Logic Manager whenever the items change their status.

Notification sends data similar to those, one can get using UC API or LM API state. There is one difference for a sensors:: s ensor with error status (status = -1) does not send its value data until the value is null. This was done specifically for the logic components to work correctly with the old value until the sensor status data is updated correctly and the sensor is back online or until the data is expired.

action - unit and macro action events

Every time the unit action or macro action changes its status, the notification server receives “action” event notification.

Notification sends data similar to ones that can be obtained using UC API result command.

log - logged event

When the system or you add record to the logs, the notification system sends ‘log’ event notification. The log notification data have the following format:

{
 "h": "<SYSTEM_NAME>",
 "l": <LEVEL>,
 "p": "<PRODUCT_CODE>",
 "msg": "<message body>",
 "mod": "<MODULE>",
 "th": "<MODULE_THREAD>",
 "t": <TIME(UNIX_TIMESTAMP)>
}
  • SYSTEM_NAME the name specified in the configuration file of controller (or hostname by default)
  • LEVEL 10 - DEBUG, 20 - INFO, 30 - WARNING, 40 - ERROR or 50 for CRITICAL
  • PRODUCT_CODE “uc” for Universal Controller, “lm” for Logic Manager, “sfa” for SCADA Final Aggregator
  • MODULE a specific system module, e. g. ‘unit’
  • MODULE_THREAD the module thread, e. g. “_t_action_processor_lamp1”

Important: the system does not send the log records related to the notification system itself. They are not visible via EI interfaces and are written only into the local log files. This was done for the notification system not to send the records in cycles.

Configuring the notification endpoints

Configuration is done using the console commands uc-notifier for Universal Controller, lm-notifier for Logic Manager and sfa-notifier for SCADA Final Aggregator. Therefore, even if two controllers are set up in the same folder on the same server, they have different notification endpoints configurations.

Basic Configuration

Let’s play with notification system e.g. of Universal Controller. This command will give us the list of notifiers, including their types, IDs, status and endpoint target.

# uc-notifier list

Type ID Status Target
mqtt eva_1 Enabled eva:test@localhost:1883/lab

Let’s test the endpoint (for mqtt the system will try to publish [space]/test)

# uc-notifier test eva_1 OK

To create the new notifier configuration, run:

uc-notifier create [-s SPACE] [-t SEC] [-y] ID PROPS

where

  • ID the unique ID of the notifier
  • PROPS endpoint properties, e.g. mqtt:[username:password]@host:[port]
  • -s SPACE notification space
  • -t SEC timeout (optional)

Option “-y” enables the notification configuration right after creation (by default all notifiers are created as disabled)

The notifier configuration params may be viewed with props and changed with set notifier cli commands. To apply the changes you must restart the controller.

Except for endpoint configuration, notifiers have some additional params:

  • skip_test if “true”, the endpoint won’t be tested at the controller start (the controller keeps the notifier active but puts error into the log)
  • notify_key notification key for custom http endpoints
  • collect_logs this should be set to “true” for SCADA Final Aggregator MQTT notifiers if you want to collect the logs of other controllers and have the records available locally in SFA.
Subscribing the notifier to events

By default, the new notifier is not subscribed to any events. You can review all the subscriptions using “get_config” command.

To subscribe notifier to the new subject, run:

uc-notifier subscribe <subject> <notifier_id> [args]

(where subject is “state”, “log” or “action”)

When subscribing notifier to logs, you may use optional -l LEVEL param (10 - DEBUG, 20 - INFO, default, 30 - WARNING, 40 - ERROR, 50 - CRITICAL).

When subscribing notifier to state changes, you may also always specify item types (comma separated) or use ‘#’ for all types with -v TYPE param, groups with -g GROUPS. Optionally you may specify the particular items to subscribe notifier to with -I ITEMS.

Note

For each “state” subscription you must specify either type and groups or item IDs.

Example:

uc-notifier subscribe state test1 -v ‘#’ -g ‘hall/#’

subscribes the notifier test1 to the events of the status change of all the items in the hall group subgroups.

Subscription to “action” requires the params similar to “state”. Additionally, -a ‘#’ should be specified to subscribe to all the action statuses or -a state1,state2,state3… to subscribe to the certain statuses of the queued actions:.

For example, the following command will subscribe the notifier to the events of all failed actions:

uc-notifier subscribe action test2 -v '#' -g '#' -a dead,refused,canceled,ignored,failed,terminated

Once created, the subscription can’t be changed, but new subscription to the same subject replaces the configuration of the previous one.

To unsubscribe the notifier from the subject, run:

uc-notifier unsubscribe [subject] <notifier_id>

if the subject is not specified, the notifier will be unsubscribed from all notification subjects.

The controller should be restarted to apply the new subscriptions configuration.

MQTT (mqtt)

MQTT is a major endpoint type used to link several EVA subsystems. For instance, it enables Logic Manager and SCADA Final Aggregator controllers to receive the latest item status from Universal Controller servers. We test and use EVA with mosquitto server, but you can use any server supporting MQTT protocol. As far as MQTT is the major type of the EVA notification system, let us examine it in detailed.

MQTT and state notifications

Items in MQTT form a subject hive so-called “EVA hive”. Hive may have a space e.g. “plant1/” to separate several EVA systems which use the same MQTT server.

Item’s state is stored in a hive with the subject SPACE/item_type/group/item_id and contains the item state data and some configuration params in the subtopics.

MQTT and action notifications

Unit action notifications are sent to the topic

SPACE/unit/group/UNIT_ID/action

Logic macros action notifications are sent to the topic

SPACE/lmacro/group/UNIT_ID/action

These messages include the serialized action information in JSON format. As soon as action state is changed, the new notification is sent.

MQTT and log notifications

Log messages are sent to the MQTT server as JSON with the following MQTT subject:

SPACE/log

It means that the common log subject is created for one EVA space.

Any EVA server (usually it’s a job for SCADA Final Aggregator) can be a log collector, collecting the reports from MQTT server (space/log), pass them further via the local notification system and have them available via API. In order to enable this function, set param collect_logs to true in the notifier configuration:

sfa-notifier set eva_1 collect_logs true
Setting up MQTT SSL

If MQTT server requires SSL connection, the following notifier properties should be set:

  • ca_certs CA certificates file (e.g. for Debian/Ubuntu: /etc/ssl/certs/ca-certificates.crt), required. SSL client connection is enabled as soon as this property is set.
  • certfile SSL certificate file, if required for authentication
  • keyfile SSL key file for SSL cert
Setting up MQTT QoS

You may specify different MQTT QoS for events with different subjects.

To set the same QoS for all events, use command:

uc-notifier <notifier_id> set qos <Q>

(where Q = 0, 1 or 2)

To set QoS for the specified subject, use command:

uc-notifier <notifier_id> set qos.<subject> <Q>

e.g.

uc-notifier eva_1 set qos.log 0

Quick facts about MQTT QoS:

  • 0 the minimum system/network load but does not guarantee message delivery
  • 1 guarantees message delivery
  • 2 the maximum system/network load which provides 100% guarantee of message delivery and guarantees the particular message has been delivered only once and has no duplicates.
Use MQTT for updating the item states

MQTT is the only EVA notifier type performing two functions at once: both sending and receiving messages.

Control and monitoring items can use MQTT to change their state (for synchronization) if the external controller can send active notifications under this protocol.

The items change their state to the state received from MQTT, if someone sends its state update to EVA hive with “status” or “value” subtopics.

To let the item receive MQTT state updates, set its mqtt_update configuration param to the local MQTT notifier ID, as well as additionally optionally specify MQTT QoS using a semicolon (i.e. eva_1:2). QoS=1 is used by default.

One item an be subscribed to one MQTT notifier to get the state updates, but different items on the same controller can be subscribed to different MQTT notifiers.

When remote controller is connected, Logic Manager and SCADA Final Aggregator have copies of the remote items and it’s better to sync them in real time. The MQTT notifier where state updates are received from is set in mqtt_update configuration param of the connected controller, the value mqtt_update_default from lm.ini/sfa.ini is used by default.

MQTT and unit actions

MQTT can be also used as API to send actions to the units. In order to send an action to the unit via MQTT, send a message with the following subject: [space]/<group>/<unit_id>/control and the following body:

status value priority

value and priority parameters are optional. If value should be omitted, set it to “none”.

In case you need 100% reliability, it is not recommended to control units via MQTT, because MQTT can only guarantee that the action has been received by MQTT server, but not by the target Universal Controller. Additionally, you cannot obtain action uuid and further monitor it.

To let unit responding to MQTT control messages, set its configuration param mqtt_control to the local MQTT ID. You may specify QoS as well via semicolon, similarly as for mqtt_update.

DB Notifiers

EVA ICS has a special notifier type: db, which is used to store items’ state history. State history can be obtained later via API calls or SFA Framework for analysis and e.g. to build graphical charts.

To create db notifier, specify notifier props as db:<dbfile>[:keeptime], e.g. db:history1.db:604800, where history1.db - database file in runtime folder, 604800 - seconds to keep archive records (1 week). If keep time is not specified, EVA keep records for last 86400 seconds (24 hours).

After creating db notifier, don’t forget to subscribe it to state events. Events action and log are ignored.

If easy-setup is used for EVA installation, notifier called db_1 for SFA is created automatically.

History database format is sqlite3.

HTTP Notifiers

JSON (json)

HTTP notifications (aka web hooks) can be transferred to servers which, for some reasons, cannot work with MQTT in real time, e.g. servers containing third-party or your own web applications.

JSON notifier type is equal to HTTP/POST, the only one difference is that data is sent fully in JSON format:

  • k notification key the remote app may use to authorize the sender
  • subject event subject
  • data event data array

Your application must respond with JSON if the event has been processed successfully:

{ "result" : "OK" }

or if your app failed to process it:

{ "result" : "ERROR" }

The event data field is always an array and may contain either one event or several ones.

When EVA controllers test remote http-post endpoint, they send notifications with subject=”test” and the remote app should respond with { “result”: “OK” }.

As HTTP/POST notifier type is no longer supported, we recommend creating web hooks only with JSON notifier type.

HTTP/POST (http-post, deprecated)

Note

This notifier type is deprecated and will be removed in the future versions. Please switch all your existing web hooks to JSON notifiers.

http-post notifier sends data to the URI specified in the configuration with POST method, as www-form and in the following format:

  • k notification key the remote app may use to authorize the sender
  • subject event subject
  • data event data array in JSON format

Your application must respond with JSON if the event has been processed successfully:

{ "result" : "OK" }

or if your app failed to process it:

{ "result" : "ERROR" }

The event data field is always an array and may contain either one event or several ones.

When EVA controllers test remote http-post endpoint, they send notifications with subject=”test” and the remote app should respond with { “result”: “OK” }.

HTTP/GET (http, deprecated)

Note

This notifier type is deprecated and will be removed in the future versions. Please switch all your existing web hooks to JSON notifiers.

As with http-post, event notification can be transferred to remote apps using HTTP/GET method. In this case only one event notification can be sent at once.

ET notifications are similar to POST except that k (key), s (subject of the message) and all the data fields are transferred directly in the query string.

Example:

GET http://server1/notify.php?k=secretkey&s=state&group=env&id=temp1&status=1&value=29.555&type=sensor&space=office

Your application must respond with JSON if the event has been processed successfully:

{ "result" : "OK" }

or if your app failed to process it:

{ "result" : "ERROR" }

When EVA controllers test remote http-post endpoint, they send notifications with subject=”test” and the remote app should respond with { “result”: “OK” }.

http notifier configuration is similar to http-post one, except that the latter has one additional parameter: stop_on_error. If it’s set to true, when multiple notifications are sent at once, the system will stop sending them as soon as one of the notifications fails to be delivered.

HTTP/GET (http) is the simplest type of the notification server for personal use. It requires neither knowledge of some additional protocols nor JSON decoding, your app may obtain all the data from the request query string.

Command line interfaces

EVA command line apps

EVA apps are used to configure the system and call controller API functions from the command line or by external scripts. All of the following apps are located in bin folder.

EVA shell

EVA shell (eva-shell) is a primary CLI tool. It allows you to manage local system as well as calling other tools directly from CLI interactive command line.

With EVA shell you can install updates, backup and restore configuration, start and stop EVA components.

Universal Controller
Logic Manager
SCADA Final Aggregator
Other
  • test-uc-xc a special app to test UC item scripts. Launches an item script with UC cvars and EVA paths set in the environment.
  • sbin/layout-converter allows to convert simple item layout to enterprise.

Virtual item management is performed using xc/evirtual application.

Legacy

In case of significant changes in the commands or arguments, previous versions of command line tools are kept and moved to legacy folder. We strongly recommend using API calls only in all 3rd-party applications, but if your app uses command line interface, you can get the previous version until the app is reprogrammed to use a new one.

Device control apps

EVA distribution includes preinstalled samples for device controlling. All sample scripts are located in xbin folder

TCP/IP controlled relays
  • EG-PM2-LAN controls EG-PM2-LAN Smart PSU
  • SR-201 controls the SR-201 relay controllers - a quite popular and simple solution with TCP/IP management option
1-Wire
  • w1_ds2408 controls Dallas DS2408-based relays on the local 1-Wire bus
  • w1_therm monitors Dallas DS18S20, DS18B20 and other compatible temperature sensors on the local 1-Wire bus
  • w1_ls displays the devices connected to the local 1-Wire bus

Tutorial

In this section, we will focus on EVA configuration which is illustrated by the following example.

There is a room and some equipment:

  • Internal ventilation system, powered via SR-201 controlled relay, port 1
  • External ventilation system, powered via SR-201 controlled relay, port 2
  • Air temperature sensor Dallas DS18S20
  • a motion sensor in the hall connected to AKCP SensorProbe
  • a light in the hall connected to Denkovi SmartDEN IP-16R, port 2
  • and some alarm system installed on a remote server and called by its own API (via GET request). As soon as the alarm is activated, it switches on the alarm siren and sends an SMS to the operator.

Our task is to automate the above:

  • To switch on the internal ventilation every night for the period from 9pm till 7am.
  • To switch on ventilation in the daytime, if the air temperature is above 25 degrees for more than 5 minutes in a row.
  • If the sensor detects a motion, do the following:
    • if the alarm is on - send an API request to the alarm system
    • if the alarm is off - turn on the light in the hall
  • The user should be able to control the ventilation system with web interface, see the temperature, manage alarm and lighting

Let’s do this step by step, from equipment configuration to interface development. Let’s Suppose that EVA has already been installed and everything is located on a single server, including MQTT server with eva:secret access and all the data will be sent into plant1 subject.

EVA bin folder is included in system PATH.

All operations will be done using command line applications.

Note

Since EVA 3.1 command line interface was significantly changed. Old CLI tools are available in legacy folder. The examples described in tutorial use only new CLI tools.

Universal Controller configuration

So, let us proceed with our configuration. Connect the equipment to Universal Controller and configure the notification system.

Connecting ventilation

create two units for ventilation:

uc-cmd create unit:ventilation/vi -y # internal
uc-cmd create unit:ventilation/ve -y # external
Method 1: with scripts

As a first step, create the variable for controlling SR-201 in order not to write the full relay control commands anew:

uc-cmd cvar set REL1_CMD "SR-201 192.168.22.3"

As far as both ventilation systems are connected via the same relay, and it displays the states of all ports at once, let’s create a multiupdate for updating their status with a single command:

uc-cmd create mu:ventilation/mu1 -y
uc-cmd config set mu:ventilation/mu1 items unit:ventilation/vi,unit:ventilation/ve

and the script for this multiupdate named xc/uc/mu1_update:

#!/bin/sh

${REL1_CMD} | head -2

Note

After creating a script file, don’t forget to set its executable permissions (chmod +x scriptfile)

Let’s check the script:

test-uc-xc xc/uc/mu1_update
Reading custom vars from /opt/eva/runtime/uc_cvars.json

REL1_CMD = "SR-201 192.168.22.3"

Starting 'xc/uc/mu1_update'

stime: 1507923323.396813
etime: 1507923323.400836
duration: 0.004023 sec  ( 4.022598 msec )
exitcode: 0

---- STDOUT ----
0
0

---- STDERR ----
----------------

Then we need to create two scripts for the relay control.

For internal ventilation, named xc/uc/vi

#!/bin/sh

${REL1_CMD} 1 $2

and for external ventilation, named xc/uc/ve

#!/bin/sh

${REL1_CMD} 2 $2

Enable the ventilation control:

uc-cmd action enable unit:ventilation/vi
uc-cmd action enable unit:ventilation/ve

set a multiupdate to update unit states every 30 seconds

uc-cmd config set mu:ventilation/mu1 update_interval 30 -y
Method 2: with driver

Starting from EVA 3.1 you can use pre-made drivers. Let’s do the above with driver.

Download PHI module:

uc-cmd phi download https://get.eva-ics.com/phi/relays/sr201.py

Load PHI module to controller. As sr201 PHI provides aao_get feature, set update=30 to update all items which use drivers with this PHI every 30 seconds:

uc-cmd phi load relay1 sr201 -c host=192.168.22.3,update=30 -y

Let’s test it:

uc-cmd phi test relay1 self

After loading sr201 PHI automatically created driver “relay1.default” with “basic” LPI. As we have a simple logic, let’s use it as-is. Set driver to both ventilation units:

uc-cmd driver set unit:ventilation/vi relay1.default -c port=1 -y
uc-cmd driver set unit:ventilation/ve relay1.default -c port=2 -y
Connecting a temperature sensor

Create sensor in UC:

uc-cmd create sensor:env/temp1

(Consider Linux w1-gpio and w1-therm kernel modules are already loaded)

Let’s find our sensor on a 1-Wire bus:

./xbin/w1_ls
28-000006ef85d7
Method 1: with scripts

Here it is. Create a script named xc/uc/temp1_update

#!/bin/sh

VALUE=`w1_therm 28-000006ef85d7` && echo 1 ${VALUE} || echo -1

Let the temperature update every 20 seconds:

uc-cmd config set sensor:env/temp1 update_interval 20 -y
Method 2: with driver

Download PHI module:

uc-cmd phi download https://get.eva-ics.com/phi/sensors/env/w1_ds18n20.py

Load PHI module to controller. This is universal PHI which means you don’t need to specify particular sensor address when loading, it should be specified later when you set driver to sensor:

uc-cmd phi load w1t w1_ds18n20 -y

After loading w1_ds18n20 PHI automatically created driver “w1t.default” with “sensor” LPI. As we have a simple logic, let’s use it as-is. Set driver to sensor:

uc-cmd driver set sensor:env/temp1 w1t.default -c port=28-000006ef85d7 -y

As this PHI doesn’t provide aao_get feature and we can’t ask it to update sensors automatically, set update_interval sensor property to let it passively update itself every 20 seconds:

uc-cmd config set sensor:env/temp1 update_interval 20 -y
Connecting a motion sensor

Create a sensor in UC:

uc-cmd create sensor:security/motion1 -y

and configure the sensors controller to send SNMP traps to our server IP.

Method 1: with SNMP trap parser

Switch on the debugging mode and look into the log file:

uc-cmd debug on
tail -f log/uc.log|grep "snmp trap data"

Let someone walk near the sensor and we’ll catch SNMP trap data:

2017-10-13 18:52:42,568 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.2.1.1.3.0 = 549411751
2017-10-13 18:52:42,569 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.6.3.1.1.4.1.0 = 1.3.6.1.4.1.3854.1.0.301
2017-10-13 18:52:42,571 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.6.3.18.1.3.0 = 192.168.22.95
2017-10-13 18:52:42,572 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.6.3.18.1.4.0 =
2017-10-13 18:52:42,574 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.6.3.1.1.4.3.0 = 1.3.6.1.4.1.3854.1
2017-10-13 18:52:42,576 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.4.1.3854.1.7.1.0 = 4
2017-10-13 18:52:42,579 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.4.1.3854.1.7.2.0 = 1
2017-10-13 18:52:42,581 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.4.1.3854.1.7.3.0 = 0
2017-10-13 18:52:42,583 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.4.1.3854.1.7.4.0 = 0
2017-10-13 18:52:42,584 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.4.1.3854.1.7.5.0 = Motion1
2017-10-13 18:52:42,586 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.4.1.3854.1.7.6.0 = MD Hall
2017-10-13 18:52:44,583 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.2.1.1.3.0 = 549411951
2017-10-13 18:52:44,584 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.6.3.1.1.4.1.0 = 1.3.6.1.4.1.3854.1.0.301
2017-10-13 18:52:44,586 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.6.3.18.1.3.0 = 192.168.22.95
2017-10-13 18:52:44,588 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.6.3.18.1.4.0 =
2017-10-13 18:52:44,589 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.6.3.1.1.4.3.0 = 1.3.6.1.4.1.3854.1
2017-10-13 18:52:44,591 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.4.1.3854.1.7.1.0 = 2
2017-10-13 18:52:44,594 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.4.1.3854.1.7.2.0 = 0
2017-10-13 18:52:44,596 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.4.1.3854.1.7.3.0 = 0
2017-10-13 18:52:44,597 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.4.1.3854.1.7.4.0 = 0
2017-10-13 18:52:44,598 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.4.1.3854.1.7.5.0 = Motion1
2017-10-13 18:52:44,602 plant1  DEBUG uc traphandler_t_dispatcher: snmp trap data 1.3.6.1.4.1.3854.1.7.6.0 = MD Hall

As we can see, the sensor sends SNMP OID 1.3.6.1.4.1.3854.1.7.1.0 = 4 when there is some activity and 1.3.6.1.4.1.3854.1.7.1.0 = 2 when the activity is finished. If we disconnect the sensor from the sensorProbe, trap with the same OID and value 7 will be received.

switch off the debugging mode

uc-cmd debug off

append one ident var to let it parse only “its own” traps:

uc-cmd config set sensor:security/motion1 snmp_trap.ident_vars "1.3.6.1.4.1.3854.1.7.6.0=MD Hall" -y

and use SNMP OID 1.3.6.1.4.1.3854.1.7.1.0 to monitor it:

uc-cmd config set sensor:security/motion1 snmp_trap.set_down 1.3.6.1.4.1.3854.1.7.1.0=7 -y
uc-cmd config set sensor:security/motion1 snmp_trap.set_if 1,1:1.3.6.1.4.1.3854.1.7.1.0=4 -y
uc-cmd config set sensor:security/motion1 snmp_trap.set_if 1,0:1.3.6.1.4.1.3854.1.7.1.0=2 -y

The final sensor configuration will look like:

uc-cmd config get sensor:security/motion1
{
   "description": "",
   "expires": 0,
   "mqtt_update": null,
   "snmp_trap": {
       "ident_vars": {
           "1.3.6.1.4.1.3854.1.7.6.0": "MD Hall"
       },
       "set_down": {
           "1.3.6.1.4.1.3854.1.7.1.0": "7"
       },
       "set_if": [
           {
               "status": 1,
               "value": "1",
               "vars": {
                   "1.3.6.1.4.1.3854.1.7.1.0": "4"
               }
           },
           {
               "status": 1,
               "value": "0",
               "vars": {
                   "1.3.6.1.4.1.3854.1.7.1.0": "2"
               }
           }
       ]
   },
   "update_exec": null,
   "update_interval": 0,
   "update_timeout": null,
   "virtual": false
}

The sensor is ready. It doesn’t require any passive update script since its state is updated with SNMP traps by the equipment.

Method 2: with driver

Download PHI module:

uc-cmd phi download https://get.eva-ics.com/phi/sensors/alarm/akcp_md.py

Load PHI module to controller. Consider motion sensor is connected to AKCP sensor controller port #1 and it has IP address 192.168.22.5.

uc-cmd phi load md1 akcp_md -c host=192.168.22.5,sp=1 -y

If one port is specified, akcp_md creates a driver with ssp LPI, which doesn’t need any additional options. Just set it to our sensor:

uc-cmd driver set sensor:security/motion1 md1.default -y

The sensor is ready. It doesn’t require any passive updates since its state is updated with SNMP traps parsed by driver.

Connecting a hall light

Create hall light unit:

uc-cmd create unit:light/lamp1 -y
uc-cmd action enable unit:light/lamp1

Let it turn off automatically after 10 mins of inactivity:

uc-cmd config set unit:light/lamp1 auto_off 600 -y

and enable the actions to be always executed:

uc-cmd config set unit:light/lamp1 action_always_exec 1 -y
Method 1: with scripts

So, now we have to connect the lamp to Denkovi IP-16R relay. Connect it similarly to ventilation:

uc-cmd cvar set REL2_CMD "snmpset -v2c -c private 192.168.22.4 .1.3.6.1.4.1.42505.6.2.3.1.3"
uc-cmd cvar set REL2_UPDATE_CMD "snmpget -v2c -c public 192.168.22.4 .1.3.6.1.4.1.42505.6.2.3.1.3"

This relay returns the status of each port separately. Additionally, there is only one connected device and, therefore, we won’t create a multiupdate for the unit and let it update the state with its own update script.

Create a script named xc/uc/lamp1_update

#!/bin/sh

${REL1_UPDATE_CMD}.1 | cut -d: -f2 | awk '{ print $1 }'

and the action script xc/uc/lamp1

#!/bin/sh

${RELAY1_CMD}.1 i $2

Let’s update the lamp state every 30 seconds:

uc-cmd config set unit:light/lamp1 update_interval 30 -y
Method 2: with driver

Download PHI module:

uc-cmd phi download https://get.eva-ics.com/phi/relays/dae_ip16r.py

Load PHI module to controller. This is universal PHI which means you don’t need to specify particular relay host when loading, it may be specified later when you set driver to sensor. But in our example we have only one relay of such type, so let’s specify all options in PHI config:

uc-cmd phi load relay2 dae_ip16r -c host=192.168.22.4,retries=2

After loading dae_ip16r PHI automatically created driver “relay2.default” with “basic” LPI. As we have a simple logic, let’s use it as-is. Set driver to lamp unit:

uc-cmd driver set unit:light/lamp1 relay2.default -c port=2 -y

Let’s update the lamp state every 30 seconds:

uc-cmd config set unit:light/lamp1 update_interval 30 -y

Now open UC EI, check the setup, switch on/off the units, see how the sensor values are updated.

API keys configuration

Note

If you’ve used “easy setup” for the EVA installation, you may skip this step because all controllers are already connected.

Connect Logic Manager and SCADA Final Aggregator to this controller. Create two API keys for them in etc/uc_apikeys.ini:

[lm]
key = secret_for_lm
groups = #
sysfunc = no
hosts_allow = 127.0.0.1

[sfa]
key = secret_for_sfa
groups = #
sysfunc = no
hosts_allow = 127.0.0.1

After adding the new keys, you need to restart UC, which we’ll do later. Firstly, you should connect it to MQTT.

Notification system configuration

Note

If you’ve used “easy setup” for the EVA installation, you may skip this step because all controllers are already connected.

To let the item states be transferred from UC to other controllers in real time, configure its notification system. Connect the server to the local MQTT:

uc-notifier create eva_1 mqtt:eva:secret@localhost -s plaint1 -y
uc-notifier subscribe state eva_1 -v '#' -g '#'

uc-notifier config eva_1
{
    "enabled": true,
    "events": [
        {
            "groups": [
                "#"
            ],
            "subject": "state",
            "types": [
                "#"
            ]
        }
    ],
    "host": "localhost",
    "id": "eva_1",
    "password": "secret",
    "space": "plant1",
    "type": "mqtt",
    "username": "eva"
}

Looks good. Now restart UC:

./sbin/uc-control restart

The configuration of Universal Controller is complete. Let’s proceed to Logic Manager configuration.

Logic Manager configuration

So, let us proceed with our configuration. As soon as Universal Controller is already configured, let us move on with Logic Manager.

Configuring notification system and API key for SFA

Note

If you’ve used “easy setup” for the EVA installation, you may skip this step because all controllers are already connected.

The first step is to connect the server to the local MQTT to allow Logic Manager to receive UC item states in real time:

lm-notifier create eva_1 mqtt:eva:secret@localhost -s plant1 -y

then subscribe the notification system to receive the states of the local logic variables which will be created later:

lm-notifier subscribe state eva_1 -v lvar -g '#'
lm-notifier config eva_1
{
    "enabled": true,
    "events": [
        {
            "groups": [
                "#"
            ],
            "subject": "state",
            "types": [
                "#"
            ]
        }
    ],
    "host": "localhost",
    "id": "eva_1",
    "password": "secret",
    "space": "plant1",
    "type": "mqtt",
    "username": "eva"
}

Add API key for SCADA Final Aggregator in etc/lm_apikeys.ini:

[sfa]
key = secret_for_sfa2
groups = #
allow = dm_rule_props, dm_rules_list
hosts_allow = 127.0.0.1

Restart LM PLC:

./sbin/lm-control restart
Connecting UC controller

Note

If you’ve used “easy setup” for the EVA installation, you may skip this step because all controllers are already connected.

Connect the local UC to Logic Manager using the key we’ve created in etc/uc_apikeys.ini in the previous part of the tutorial:

lm-cmd controller append http://localhost:8812 -a secret_for_lm -m eva_1 -y
lm-cmd -J remote -p S
[
    {
        "controller_id": "uc/uc1",
        "group": "security",
        "id": "motion1",
        "oid": "sensor:security/motion1",
        "status": 1,
        "type": "sensor",
        "value": "0"
    },
    {
        "controller_id": "uc/uc1",
        "group": "env",
        "id": "temp1",
        "oid": "sensor:env/temp1",
        "status": 1,
        "type": "sensor",
        "value": "25.4"
    }
]

Looks correct, sensors are loaded, let’s check the units:

lm-cmd -J remote -p U

Let LM PLC reload the items from the connected controller every 60 seconds, if new ones are added in future:

lm-cmd controller set uc1 reload_interval 60 -y
Building logic

We have two tasks: to switch on the inside ventilation if the temperature is above 25 degrees, and handle the events received from the motion sensor. Do not forget that the inside ventilation should be off from 9pm till 7am. Though this will be later implemented via sfa-cmd and system cron, we should get it prepared now.

Ventilation logic

We chose our example, as far as the boundary conditions of the sensor is a very common problem for such tasks.

If we solve this problem by creating the following two rules:

  • if the temperature is above 25 degrees, the fan is switched on
  • if below - switched off

the following problem may occur: if the temperature will hover around 25 degrees, the ventilation system will constantly switch on and off. Therefore, a breakdown is highly possible. We cannot simply set up chillout_time in the rule, it completely disables the rule, so it doesn’t match after the chillout is over, if both previous and current state are in the range.

Due to the flexibility of EVA there is a number of solutions of this problem:

Option 1:

Ventilation is switched on, if the temperature is above 25 degrees, and switched off if it is below e.g. 25.5. The logic will have half a degree gap for the equipment not to be overloaded. If the temperature changes not that quickly, this option would be the best one.

Option 2:

  • Create the rule without a condition activated whenever the env/temp1 sensor changes its value
  • The stop-start logic, as well as the temperature monitoring logic, is fully transferred to the macro executed by the above rule.
  • macro reads the value of lvar ventilation/start_temp (to avoid hardcoding 25 in a macro code and let it have an ability to be changed from outside)
  • To avoid the continuous running of macro, use the rule prop chillout_time. Or even
  • Use unit_status macro function to get the current ventilation status and use macro lock function to block its changing too often e.g. for 5 minutes

The macro code will look like:

if status('unit:ventilation/vi') and \
    value('sensor:env/temp1') < value('ventilation/start_temp'):
  try: lock('ventilation/vi/control', 5, 300)
  except: exit()
  stop('ventilation/vi')
elif not status('unit:ventilation/vi') and \
    value('sensor:env/temp1') >= value('ventilation/start_temp'):
  try: lock('ventilation/vi/control', 5, 300)
  except: exit()
  start('ventilation/vi')

Option 3

Increase update_interval prop of env/temp1 sensor e.g. to 300 seconds. This option will work, though it is not that good because the system will obtain the current temperature with a 5 min delay (or we need to duplicate the sensor in UC and create a quicker one).

Option 4

Add a 5 minutes delay at the end of xc/uc/vi action script, allow vi queues (set action_queue=1 unit prop), and start ventilation from the macro in the following way:

try: lock('ventilation-example3', 5, 10)
except: exit()
# clean up all queued action
q_clean('ventilation/vi')
# exec our action
start('ventilation/vi')
unlock('ventilation-example3')

Not bad, but we loose an ability to exec the actions with w param and get the correct completion status.

Option 5

Use system crontab to copy env/temp1 value to some logic variable every 5 minutes. Then work only with this logic variable. This option is too awkward, the logic of the system crontabs will sooner or later turn into the script hell. Therefore, we will use cron only for the time schedule-based logic, everything else will be done using EVA.

Option 6 - the best one

There are more than 10 options to solve our problem, but we will choose the best one: delayed start. Moreover, we have a condition to run ventilation only in 5 minutes after the temperature becomes >=25. So, we will use this option. The other ones have been reviewed just because this is a tutorial.

Create two logic variables:

lm-cmd create lvar:ventilation/vi_auto -y
lm-cmd create lvar:ventilation/vi_timer -y
lm-cmd set vi_auto -s 1 -v 1

The first one will act as a flag for the ventilation control macro: if the flag is on 1, the control is possible, if off - the ventilation should not be touched. The second variable will act as a delayed start timer.

Create a control macro which satisfies for our current task and the scheduled ventilation switching. We will give it two parameters: the first - what to do with ventilation (0 - switch off, 1 - switch on), the second - who runs it: temperature event, our delayed start timer or a system cron:

lm-cmd macro create control/vi_control -y

Put a macro code in xc/lm/vi_control.py file

if _1: # if anyone asks to switch on the ventilation
    if _2 == 'event': # if it's an event
    reset('ventilation/vi_timer') # reset delayed start timer
    elif _2 == 'timer': # if it's a timer
        if value('ventilation/vi_auto'):
            start('ventilation/vi') # start ventilation if allowed
    elif _2 == 'cron': # if it's a system cron
        # disable the ventilation automation for everyone but cron
        clear('ventilation/vi_auto', 0):
        start('ventilation/vi') # start ventilation
else: # if it's a command to switch off
    clear('ventilation/vi_timer') # stop the delayed start timer
    if value('ventilation/vi_auto') or _2 == 'cron':
        # in case the command is send by cron or
        # if allowed to stop - stop it
        stop('ventilation/vi')
    if _2 == 'cron':
        # if the ventilation is switched off by cron
        # then enable automation back for everyone
        set('ventilation/vi_auto', 1)

The macro requires 3 rules:

The first one will match if the temperature is above or equal to 25 degrees and activates the delayed start timer:

lm-rules add -E -y -x value --type sensor --group env --item temp1 --ge 25 --initial any -m vi_control -a "1 event"

The second one will match if the temperature is below 25 degrees and switches the ventilation off (if it’s allowed):

lm-rules add -E -y -x value --type sensor --group env --item temp1 --lt 25 --initial any -m vi_control -a "0 event"

The third rule will run the macro to turn the ventilation on as soon as the delayed start timer finishes the countdown:

lm-rules add -E -y --type lvar --group ventilation --item vi_timer --exp -m vi_control -a "1 timer"
Motion sensor logic

We will need one variable identifying whether the alarm is switched on or not:

lm-cmd create lvar:security/alarm_enabled -y
lm-cmd set alarm_enabled -s 1 -v 0

Additionally, we will need two macros. The first one will send API call to alarm system:

lm-cmd macro create security/alarm_start -y

put its code to xc/lm/alarm_start.py:

# do not send API calls more than once in 30 minutes
try: lock('alarm-start', expires = 1800)
except: exit()
# call the alarm system API
get('http://alarmserver/api/activate?apikey=blahblahblah')

The second one will check whether the alarm is switched, to either switch on the alarm system or just turn on the lighting:

lm-cmd macro create security/motion1_handler -y

put its code to xc/lm/motion1_handler.py:

if value('security/alarm_enabled'):
    run('security/alarm_start')
else:
    start('light/lamp1')

Plus the additional rule executing motion1_handler macro when the motion sensor detects an activity:

lm-rules add -E -y -x value --type sensor --group security --item motion1
--eq 1 -m motion1_handler

The logic is set up. We can review and test it in LM EI and configure SCADA Final Aggregator configuration to interact with the external applications (in our case - the system cron, for the scheduled ventilation control) and serve the system web interface.

SCADA Final Aggregator configuration

So, let us proceed with our configuration. Universal Controller has already been set up and Logic Manager as well. Therefore, let us move on to SCADA Final Aggregator configuration.

Since the logic has already been implemented, we have only two tasks: to interconnect all the controllers and connect the system cron that will control the ventilation schedule.

Notification system configuration

Note

If you’ve used “easy setup” for the EVA installation, you may skip this step because all controllers are already connected.

The first step is to connect the server to the local MQTT to allow SCADA Final Aggregator to get the state of UC and LM PLC items in real time:

sfa-notifier create eva_1 mqtt:eva:secret@localhost -s plant1 -y

We won’t subscribe the notifier to anything, because all the data are received from it, and there is nothing to send instead.

sfa-notifier config eva_1
{
    "enabled": true,
    "host": "localhost",
     "id": "eva_1",
    "password": "secret",
    "space": "plant1",
    "type": "mqtt",
    "username": "eva"
}

Restart SFA:

./sbin/sfa-control restart
Connecting controllers

Note

If you’ve used “easy setup” for the EVA installation, you may skip this step because all controllers are already connected.

The next step is to connect the local UC and LM PLC to SCADA Final Aggregator with the keys created specifically for SFA:

sfa-cmd controller append http://localhost:8812 -a secret_for_sfa -m eva_1 -y
sfa-cmd controller append http://localhost:8817 -a secret_for_sfa2 -m eva_1 -y
sfa-cmd -J remote -p S
[
    {
        "controller_id": "uc/uc1",
        "group": "security",
        "id": "motion1",
        "oid": "sensor:security/motion1",
        "status": 1,
        "type": "sensor",
        "value": "0"
    },
    {
        "controller_id": "uc/uc1",
        "group": "env",
        "id": "temp1",
        "oid": "sensor:env/temp1",
        "status": 1,
        "type": "sensor",
        "value": "25.4"
    }
]

Looks fine, sensors are loaded, let’s check units and logic variables:

sfa-cmd -J remote -p U
sfa-cmd -J remote -p LV

Let SFA reload the items from the connected controllers every 60 seconds, if new ones are added in future:

sfa-cmd controller set uc/uc1 reload_interval 60 -y
sfa-cmd controller set lm/lm1 reload_interval 60 -y
Connecting external applications

There is only one external application - system cron. We won’t connect it via SFA API, but simply by running sfa-cmd console application.

We provide this example for one reason: you should always connect your external applications to SFA only. Controllers may be changed and, therefore, the setup may be extended: for example, one Logic Manager may be replaced by the three ones installed on the different servers. However, the local SFA will never be changed. All you need is to connect new controllers to it, and EVA item infrastructure will be available again by its usual ID.

The next step it to connect cron for it to run ventilation control macro (edit /etc/crontab or user’s crontab):

0 7 * * *    root   /path/to/sfa-cmd macro run control/vi_control -a "0 cron"
0 21 * * *    root   /path/to/sfa-cmd macro run control/vi_control -a "1 cron"

As you can see, there is no rocket science here. SCADA Final Aggregator is configured by a few commands and immediately starts collecting the data and events. In turn, it will save you a lot of time by structuring your setup. Now let’s create SFA Framework interface that will be served by SFA.

Building an interface with SFA Framework

So, let us proceed with our configuration. Universal Controller, Logic Manager and SCADA Final Aggregator have already been configured.

The last step is to create the user interface with SFA Framework.

Configuring authentication keys

Note

If you’ve used “easy setup” for the EVA installation, you may skip this step because all controllers are already connected.

Create an SFA API key named operator in etc/sfa_apikeys.ini:

[operator]
key = opsecret
groups = #
pvt = #
hosts_allow = 0.0.0.0/0

and restart SFA:

./sbin/sfa-control restart
Configuring users

Create a login for the user to use with operator key:

sfa-cmd user create john verysecret operator
Create a web application

Use SFA Framework to write a simple web application to manage our example setup. Create a new index.html file and put it to ui folder:

If the item ids or other information should be hidden from unauthorized users, the additional .js files with such data may be served by SFA PVT or frontend server with additional authentication.

<html>
 <head>
   <title>Plant1 interface</title>
   <script src="lib/jquery.min.js"></script>
   <script src="js/eva_sfa.min.js"></script>
 </head>
<body>
<!-- simple authentication form -->
 <div id="loginform">
    <form onsubmit="do_login(); return false">
    Login:
        <input type="text" size="10" name="login" id="i_login" value="" />
        <br />
    Password:
        <input type="password" size="10" name="password"
            id="i_password" value="" /><br />
         <input type="submit" name="submit" value="GO" />
    </form>
 </div>
 <!-- interface and controls -->
 <div id="interface" style="display: none">
  <div>Temperature: <span id="temp1"></span></div>
  <div>Internal ventilation:
    <a onclick="eva_sfa_action_toggle('ventilation/vi')" href="#">
        <span id="vi"></span></a>
  </div>
  <div>External ventilation:
    <a onclick="eva_sfa_action_toggle('ventilation/ve')" href="#">
        <span id="ve"></span></a>
  </div>
  <div>Hall light:
    <a onclick="eva_sfa_action_toggle('light/lamp1')" href="#">
        <span id="lamp1"></span></a>
  </div>
  <div>Alarm system:
    <a onclick="eva_sfa_toggle('security/alarm_enabled')" href="#">
        <span id="alarm_enabled"></span></a>
  </div>
  <div style="margin-top: 30px">
    <a onclick="eva_sfa_stop(show_login_form)" href="#">logout</a>
  </div>
 </div>
 <script type="text/javascript">

 var ed_labels = [ 'DISABLED', 'ENABLED' ];

 // function starting SFA Framework after the
 // authentication form has been submitted
 function do_login() {
   $('#loginform').hide();
   eva_sfa_login = $('#i_login').val();
   eva_sfa_password = $('#i_password').val();
   eva_sfa_start();
 }

 // function displaying the authentication form
 function show_login_form(data) {
   eva_sfa_password = '';
   $('#interface').hide();
   $('#i_login').val(eva_sfa_login);
   $('#i_password').val('');
   $('#loginform').show();
   }

 // after the page is loaded
 $(document).ready(function() {
   // initialize SFA Framework
   eva_sfa_init();
   // after the authentication succeeds the main interface is displayed
   eva_sfa_cb_login_success = function(data) { $('#interface').show(); }
   // if there is a login error
   eva_sfa_cb_login_error = function(data) {
       // end session and display the authentication form,
       // if auth data is incorrect
       if (data.status == 403) {
           show_login_form();
           } else {
           // otherwise - repeat the attempts every 2 seconds
           // until the server responds
           setTimeout(eva_sfa_start, 1 * 2000);
           }
       }
   // register the event handlers
   eva_sfa_register_update_state(
        'sensor:env/temp1', function(state) {$('#temp1').html(state.value)})
   eva_sfa_register_update_state(
        'unit:ventilation/vi',
        function(state) {$('#vi').html(ed_labels[state.status])});
   eva_sfa_register_update_state(
        'unit:ventilation/ve',
        function(state) {$('#ve').html(ed_labels[state.status])});
   eva_sfa_register_update_state(
        'unit:light/lamp1',
        function(state) {$('#lamp1').html(ed_labels[state.status])});
   eva_sfa_register_update_state(
        'lvar:security/alarm_enabled',
        function(state) {$('#alarm_enabled').html(ed_labels[state.value])});
 })
 </script>
</body>
</html>

Our setup is complete. Interface is available at the following address:

http(s)://<IP_address_SFA:Port>/

The default port for SFA is 8828.

FAQ

Is this a “smart home”?

EVA is a universal system used for the construction of both “smart home” and automated control system (ICS) in an industrial enterprise.

In fact, most products positioning themselves as “smart home” are just dumb control panels with a web interface. Moving light switch from the wall to the screen doesn’t make it “smart”. In turn, our system is equipped with a powerful and constantly evolving Logic Manager for automatic decision-making.

Is this IoT?

In a way, yes. As far as IoT is a part of the automation process, we are a part of IoT, in turn. However, from the thoursands of components positioning themselves as “IoT”, we choose only those working:

  • Reliably
  • Safely
  • Autonomously

And, most preferably, having an open API to be connected to any system.

The classic situation typical for IoT market involves controlling your device via the Internet with the help of software on the developer servers (aka “clouds”). As you can see it is:

  • Unreliable
  • Dangerous
  • Not autonomous

Moreover, you can’t seriously use it even at home. Our system doesn’t require registration, we don’t need your phone number, email or home address - just download, install and use it. The system works only for you and sends data to no one but you. The code is fully open, so that you can control everything that’s going on. The system doesn’t require Internet access. Everything works fine in the secure local network.

If the system is free and open, what is your interest?

We have been engaged in the automation activities for both private and enterprise sectors for many years. Our clients work with different systems, including previous versions of EVA. The development of EVA 3 is a step towards unification of all configurations that we support and use ourselves.

We decided to distribute the new system completely free of charge, but we provide commercial support for setups where reliability is of critical importance. We are also developing and integrating complex configurations, create additional commercial EVA applications and interfaces for specific systems.

In addition, if:

  • you want to become a system integrator and provide commercial support
  • you want to distribute our system together with your device
  • you want to make us an offer we can’t refuse

you should contact us and we will make a siutable offer to you.

Is EVA complex and difficult to learn?

We try to make our system as simple and user-friendly as possible even for those who have never encountered the process automation.

Is it reliable?

It is reliable enough for home and office setups, as well as for small industrial configurations, but if you use EVA on an enterprise, you should at least understand the system well.

How about security?

Traditionally, automation systems and protocols have been designed as not completely secure. As for reliability, you may agree that it would be a pity if motor or door gets stuck because of incorrect access rights to a folder or an expired SSL certificate. We recommend to have an optimal balance between stability and security and it’s really different for every setup.

What programming languages can I use to write scripts and macros?

As for scripts - you can use any. A script or a program runs as a separate process in the system. It’s only necessary that it reads the input parameters correctly and writes the result in an appropriate format. If you need speed - PERL or DASH are preffered. If speed is not so important, you may use BASH or even PHP.

Logic Manager macros are written in Python, but in most cases, you’ll need to call the internal macro functions only.

Besides, you can create your own applications working through API. The distribution includes API clients for Python and PHP.

What if I have zero experience in programming?

Programming for EVA is only about creating item management scripts. You can find plenty of examples in the documentation.

Additionally you need to program Logic Manager macros for process automation. However, most macros have a very simple structure and call the in-built set of functions.

For example, a macro that runs a pump for watering plants:

# call API action for pump1, controller will be identified automatically
start('farm/pumps/pump1')
# reset the timer for watering
reset('farm/pump1_run')
# message to the log file
info('watering cycle has been started')
# assign "watering" value to the production cycle variable
set('farm/pump1_cycle', 'WATERING')

As you can see, it is not rocket science.

What automation is all about? What are automation components?

The automation components mostly look like relay block, “smart” sockets, “smart” switches - however, there is still some kind of relay inside. Usually, there are 3 ports in the relay: input, two outputs, and two states: open and closed. In the first state, the signal passes through the first output, in the second one - through the second. This is the main principle automation is based on.

Sometimes equipment may include controlled resistors, so that additional parameters (e.g. light dimmer) should be set. In this case, you should send additional value to the controller, e.g. to set a definite percent of capacity.

Our system works not with relay, but with endpoint equipment that is automated. The relay ports are programmed and switched with the help of scripts, which are written once during installation. Thereafter, the system works with the units.

Besides, any automation system has its “eyes” and “ears” for receiving data from the environment and making its own decisions - humidity and temperature sensors, motion sensors etc.

In EVA, all decisions are made either by the user or Logic Manager subsystem.

Is EVA ICS IEC 61131-3 compliant?

EVA Logic Manager is a “cloud” PLC and can’t handle events in real-time. EVA ICS is not a hardware PLC replacement, it brings together equipment of different kind and automates the tasks usually performed by the system operator manually.

However new local nearly real-time PLC and support of ST and FBD languages are planned in the next releases. We are hardly working on it!

Where is the interface? I want a web interface!

Each automated setup needs an interface. EVA has a very powerful SCADA Final Aggregator component, which combines the whole setup itself and provides a flexible SFA Framework which allows you to create a modern websocket-powered web interface with a few strings of javascript

eva_sfa_apikey = 'MY_VERY_SECRET_KEY';
eva_sfa_init();
eva_sfa_start();
eva_sfa_register_update_state('sensor:env/temperature1',
    function(state) {
        $('#temp1').html(state.value);
    }

no rocket science as well.

High-load environments

Tuning EVA ICS

To use EVA ICS in high-load environments, remember the following:

  • Always turn off debug mode. Debug mode significantly slows down all EVA ICS components.
  • Make sure no notifiers are subscribed to the frequently (more than ~10 times per second) updated item states. If you need to synchronize states of such items, reduce remote controller reload intervals instead but set them higher than 1 second.
  • Unsubscribe notifiers from INFO log messages and actions
  • Replace db_updates = instant in Universal Controller configurations with on_exit or manual.
  • If you don’t need action history, set keep_action_history in controllers’ configuration to zero to disable it.
  • Turn off logging (comment log_file property in configuration) and reduce keep_logmem value.
  • If you use passive updates, set polldelay to the minimal value (0.001 - 0.005 for 1-5ms)

Hardware

EVA ICS is written in Python 3. It is not the fastest programming language in the world, but thanks to EVA ICS architecture and optimization for modern multi-core CPUs, the platform can provide good results even on a microcomputers. System components and CLI tools may require more time to launch on architectures different than Intel x86_64, but the regular performance should not be affected even on an embedded ARM-based systems.

Benchmarks

Benchmark tests for Universal Controller can be performed with tests/benchmark-uc-crt tool. Benchmark results may be different on a systems with different background load, so please stop all unnecesseary processes before starting a test.

The primary parameter for UC which’s bening benchmarked is a time, required for the controller to:

  • obtain item state from driver
  • perform a simple in-core event handling (convert item value to float and then compare with a float number) with self thread-locking
  • get action requiest from event handler and execute it using another driver

The time between a moment when the first driver gets new item value and a moment when the second driver is ready to call equipment action is named Core Reaction Time (CRT).

The benchmark tool for Universal Controller turns on internal controller benchmark, performs 1000 CRT tests with 30ms delays on a single sensor/unit pair and displays the average CRT value in milliseconds.

The benchmark is performed on virtual drivers, so the actual system reaction time may be higher than CRT, depending on the equipment connected.

Warning

It’s not recommended to perform a real benchmarking tests on SOHO and light industry relays due to their limited lifetime (~100-200k switches)

Below are benchmark results on a test systems (lower CRT is better):

System CPU Cores EVA ICS CRT, ms
VMWare ESXi 5.5 Intel Xeon E5630 2.53GHz 1 3.1.1 2018101701 4.5
VMWare ESXi 5.5 Intel Xeon E5630 2.53GHz 4 3.1.1 2018101701 3
VMWare ESXi 5.5 Intel Xeon D-1528 1.90GHz 1 3.1.1 2018101701 5
VMWare ESXi 5.5 Intel Xeon D-1528 1.90GHz 4 3.1.1 2018101701 3.5
Supermicro X9SXX Intel Xeon E3-1230 V2 3.30GHz 8 3.1.1 2018101701 4
Supermicro E100 Intel Atom E3940 1.60GHz 4 3.1.1 2018101701 8.5
Raspberry Pi 1A ARMv6 rev 7 v6l 1 3.1.1 2018101701 110
Raspberry Pi 2B ARMv7 rev 5 v7l 4 3.1.1 2018101701 22.5
Raspberry Pi 3B+ ARMv7 rev 4 v7l 4 3.1.1 2018101701 21
UniPi Axon S115 ARMv8 Cortex-A53 4 3.1.1 2018101701 27

Universal Controller

EVA Universal Controller (UC) is a control and monitoring subsystem.

It should be installed if you actually want to control something. UC is controlled via UC EI web interface or uc-cmd console application. Additionally, it can be integrated into other subsystems and third-party programs using UC API.

Universal Controller subsystem

You may use Universal Controller independently or integrate it with other EVA subsystems.

Units receive control actions, the controller forms them into queues and executes them using external scripts. If necessary, it terminates the current script and keeps the command history.

Additionally, Universal Controller collects data from the connected items using active and passive status updates.

Universal Controller can control equipment directly and/or act as a gateway for other local controllers.

Item status and values are stored in the local database. Other subsystems or third-party programs can read them using UC API.

Units and sensors are controlled via UC EI interface, configured via UC API. States are controlled and updated using drivers and item scripts.

All changes of item status, current control commands, and progress logs are sent to the notification system.

UC POLL DELAY

EVA is a real-time system. Being one of its essential components, UC also follows this rule. The value of poll delay is set in configuration in seconds (milliseconds using dot), e.g. 0.01.

The value of POLL DELAY means that all timeouts and events in the system should fulfill their functions for not more than TIME_SET + POLL DELAY.

Reducing POLL DELAY will increase the CPU load on the server; in turn, if increased, the UC reaction time will be longer. Recommended values: 0.1 for home and office, 0.01 and less - for industrial applications.

The optimum value of POLL DELAY for UC can be set via uc-cmd, or by manually calling UC API functions and comparing reaction/execution time of the commands.

The minimum value of POLL DELAY is 0.001 (1 millisecond).

etc/uc.ini configuration file

uc.ini - primary configuration file of UC server

[server]
; system name (default - hostname)
; name = eva-uc1
; system polldelay
polldelay = 0.01
; pid file (default var/uc.pid)
pid_file = var/uc.pid
; log file
log_file = log/uc.log
; db updates - instant, manual or on_exit
db_update = instant
; db file, default runtime/db/uc.db
db_file = runtime/db/uc.db
; user db file, default = db_file
; one user db may be used by multiple controllers at once
;userdb_file = runtime/db/users.db
; keep action history in seconds
keep_action_history = 86400
; keep memory log in seconds
keep_logmem = 86400
; notify state on start
notify_on_start = yes
; debug mode
debug = no
; create crash dump on critical errors
dump_on_critical = yes
; stop server on critical errors (will be restarted via safe-run)
; always - stop on all critical errors
; core - core errors only (ignore driver critical errors)
; no - don't stop
stop_on_critical = always
; default timeout
timeout = 5
; layout (simple or enterprise)
layout = simple

; exec commands before/after config/db save,
; e.g. mount -o remount,rw / (then back to ro)

;exec_before_save =
;
;exec_after_save =

; default mqtt notifier for updates for new items
;mqtt_update_default = eva_1:2

[sysapi]
; enable remote file management functions
;file_management = yes 

[webapi]
; web api listen on IP/port
listen = 0.0.0.0:8812
;ssl_listen = 0.0.0.0:8813
;ssl_module = builtin
;ssl_cert = test.crt
;ssl_key = test.key
;ssl_chain = test.pam
; session timeout
;session_timeout = 3600
; server thread pool
;thread_pool = 15
; uncomment to disable UC EI
;ei_enabled = no
; uncomment to use frontend X-Real-IP header to get client real IP address
;x_real_ip = yes

[snmptrap]
; snmp trap handler, default community is eva
listen = 127.0.0.1:162
community = eva
; hosts, allowed to send snmp traps
hosts_allow = 127.0.0.0/8

[udpapi]
; udp api
listen = 127.0.0.1:8881
; hosts, allowed to send UDP commands
hosts_allow = 127.0.0.0/8
hosts_allow_encrypted = 0.0.0.0/0

runtime/uc_cvars.json variables file

uc_cvars.json - file containing user variables passed to all commands and item scripts within the system environment.

The file contains a JSON dict:

{
 "VAR1": "value1",
 "VAR2": "value2"
}

Variables can be changed while the server is running via SYS API as well as uc-cmd cvar_get and cvar_set commands.

For example, let’s create a variable:

uc-cmd cvar set RELAY1_CMD "snmpset -v1 -c private 192.168.1.208 .1.3.6.1.4.1.19865.1.2."

After UC is started, it will become available for system environment, and unit management script on the port 2 of the given relay will be the following:

#!/bin/sh
${RELAY1_CMD}.1.2.0 i $2

It’s possible to assign different values for the variables used for different object groups with the same names, e.g. group1/VAR1, group2/VAR1 etc. In this case the variable will be available only for the specified group.

etc/uc_apikeys.ini API keys file

API access keys are stored into etc/uc_apikeys.ini file. At least one full access key named masterkey should be present for proper functioning. Important: with master key and API anyone can receive full access to the system similar to root user (or the user UC is run under), that is why it is recommended to use this key only in supervisory networks or even restrict its use to local host only.

[masterkey]
; the default master key is eva, change it!
key = eva
master = yes
hosts_allow = 127.0.0.1

;[key1]
;key = key1secret
;groups = group1/#, group2, group3/+/somegroup
;hosts_allow = 0.0.0.0/0

;[key2]
;key = key2secret
;items = unit1, unit2
;sysfunc = yes
;allow = cmd, lock, device
;hosts_allow = 0.0.0.0/0
; key will be assigned to requests from these hosts if no key provided
;hosts_assign = 192.168.1.1

;[lm]
;key = lmsecret
;groups = #
;sysfunc = yes
;allow = cmd
;hosts_allow = 127.0.0.1, 192.168.0.0/16

;[sfa]
;key = sfasecret
;groups = #
;hosts_allow = 127.0.0.1, 192.168.0.0/16

Action queues

All the unit control actions are queued right after they’re created. Item status update actions are not queued and just run in accordance with the set intervals.

Before execution, the control action is placed in two queues:

  • global queue for all actions
  • certain unit queue

All actions have their “priority” set when they are generated. The default priority is 100. The lower means the higher priority of queued action execution.

Queued action can have the following status:

  • created action has just been created and has not been queued yet
  • pending the action is placed in the global queue. The previous action status, as well as this one, are rarely found because UC API waits for the command to be placed in the queue of a certain unit and then returns the result
  • queued the action has already passed the global queue and is now waiting to be executed in the queue of a certain unit
  • refused unit rejected the action execution because of the item configuration value action_enabled=False
  • dead API failed to wait until the action is placed to the queue of a certain unit and, therefore, marked it as “dead”. Virtually, such status clearly indicates that server is seriously overloaded
  • canceled the command is canceled either from the outside or due to either unit already running the other action (in case action_queue=0 and queue is disabled) or the unit has got new action to execute and action_queue=2 (cancel and terminate the pending actions after getting a new one)
  • ignored the unit rejected the action execution, because its status/value are the same as requested to be changed to, and action_always_exec=False
  • running the action is being executed
  • failed the controller failed to execute the command
  • terminated the controller terminated the action execution due to timeout or by the external request
  • completed the action finished successfully

Startup and shutdown

To manage UC server use ./sbin/uc-control script with the following options:

  • start start UC server
  • stop stop UC server
  • restart restart UC server
  • logrotate call after log rotation to restart the logging
  • version display the server version

The controller startup/shutdown is also performed by ./sbin/eva-control which is configured during the system setup.

Logic Manager

EVA Logic Manager (LM PLC) - is a programmable logic controller.

This subsystem is designed to automatically manage hardware and decision-making process when the metrics are changed. Unlike standard PL controllers, LM PLC is a network-based controller, its mission is to control equipment in local networks and work as cloud controller via the Internet.

Decision-making system of LM PLC is event-based instead of cycle-based, however you can organize cycles with logic variables using them as a cycle timers.

LM PLC is customized and controlled via LM EI web interface or lm-cmd console application. It can also be integrated into other subsystems and third-party programs using LM API.

Additionally, you can use console applications for better control of the decision-making rules.

LM PLC additionally introduces one more control and monitoring item: logic variable (lvar). Logic variables are used to save and exchange system status data as well as function as flags and timers for organizing production cycles.

Any change in the status of units, sensors or logic variables is analyzed by the decision-making matrix of a certain LM controller. In case the rule conditions match, the controller calls the indicated macro that performs the specified actions.

Logic variables statuses are stored in the local database and can be accessed via LM API by other subsystems or third-party programs. Status changes are sent via notification system. To let LM PLC operate with the items of UCs in real time, it should be connected to MQTT server.

Since LM PLC is a part of EVA platform, its operating principles, settings, and configuration files generally match the other components.

etc/lm.ini configuration file

lm.ini - primary configuration file of LM PLC

[server]
; system name (default - hostname)
; name = eva3-lm1
; system polldelay
polldelay = 0.01
; pid file (default var/lm.pid)
pid_file = var/lm.pid
; log file
log_file = log/lm.log
; db updates - instant, manual or on_exit
db_update = instant
; db file, default runtime/db/lm.db
db_file = runtime/db/lm.db
; user db file, default = db_file
; one user db may be used by multiple controllers at once
;userdb_file = runtime/db/users.db
; keep action history in seconds
keep_action_history = 86400
; keep memory log in seconds
keep_logmem = 86400
; notify state on start
notify_on_start = yes
; debug mode
debug = no
; create crash dump on critical errors
dump_on_critical = yes
; stop server on critical errors (will be restarted via safe-run)
stop_on_critical = yes
; default timeout
timeout = 5
; layout (simple or enterprise)
layout = simple

; exec commands before/after config/db save,
; e.g. mount -o remount,rw / (then back to ro)

;exec_before_save =
;
;exec_after_save =

; default mqtt notifier for updates for new items
;mqtt_update_default = eva_1:2

[sysapi]
; enable remote file management functions
;file_management = yes

[webapi]
; web api listen on IP/port
listen = 0.0.0.0:8817
;ssl_listen = 0.0.0.0:8818
;ssl_module = builtin
;ssl_cert = test.crt
;ssl_key = test.key
;ssl_chain = test.pam
; session timeout
;session_timeout = 3600
; server thread pool
;thread_pool = 15
; uncomment to disable LM EI
;ei_enabled = no
; uncomment to use frontend X-Real-IP header to get client real IP address
;x_real_ip = yes

[mailer]
; mailer for mail() macro function
smtp = localhost
from = eva@eva3-lm1
default_rcp = admin@localhost, root@localhost

runtime/lm_cvars.json variables file

lm_cvars.json - file containing user variables passed to all logic control macros.

The file contains a JSON dict:

{
 "VAR1": "value1",
 "VAR2": "value2"
}

Variables can be changed while the server is running via SYS API as well as lm-cmd cvar get and cvar set commands.

etc/lm_apikeys.ini API keys file

API access keys are stored into etc/lm_apikeys.ini file. At least one full access key named masterkey should be present for proper functioning. Important: with master key and API anyone can receive full access to the system similar to root user (or the user LM is run under), that is why it is recommended to use this key only in supervisory networks or even restrict its usage to local host only.

[masterkey]
; the default master key is eva, change it!
key = eva
master = yes
hosts_allow = 127.0.0.1

;[key1]
;key = key1secret
;groups = group1/#, group2, group3/+/somegroup
;hosts_allow = 0.0.0.0/0

;[key2]
;key = key2secret
;items = macro1, lvar1, lvar2
;sysfunc = yes
;allow = cmd, dm_rule_props, dm_rules_list
;hosts_allow = 0.0.0.0/0
; key will be assigned to requests from these hosts if no key provided
;hosts_assign = 192.168.1.1

;[sfa]
;key = sfasecret
;groups = #
;allow = dm_rule_props, dm_rules_list
;hosts_allow = 127.0.0.1, 192.168.0.0/16

Connecting UC controllers

Logic macros and decision-making matrix work only with the items known to the controller, so Logic Manager loads information about units and sensors from the connected Universal Controllers.

Prior to UC connection, it is necessary to connect LM PLC to MQTT server, where other controllers will send the events. Logic Manager reads the list of items and their initial status from the connected controllers; further status monitoring is performed via MQTT. In case MQTT server is inaccessible or has data exchange problems, states of the items are updated on every remote controller reload (reload_interval prop).

To connect UC controllers you should use lm-cmd console command or LM API append_controller function.

When connecting, it is necessary to indicate minimum URI of the connected controller and API KEY functioning either as a master key or the key with access to certain items. If Logic Manager and UC keys are the same, the key can be set as $key (\$key in the command line). In this case, LM will use the local key of its own configuration.

lm-cmd append_controller -u http://localhost:8812 -a secretkey -y

Configurations of connected controllers are stored in the following folder: runtime/lm_remote_uc.d/

Logic Manager automatically loads the connected controller data (its ID) and saves configuration to runtime/lm_remote_uc.d/ID.json file.

Items from remote controllers are loaded at the LM PLC start and then refreshed with reload_interval frequency set individually for each connected controller. If LM PLC fails to get the item list during loading, it will use the existing one.

To control the list of the received items you can use lm-cmd or LM API function list_remote:

lm-cmd list_remote -p unit
lm-cmd list_remote -p sensor

All connected controllers have the following properties that can be changed while LM PLC is running:

  • description optional description of the controller
  • key API key used to access the connected controller
  • mqtt_update MQTT notifier through which items update their status
  • reload_interval interval (seconds) to reload the item list from the server, 0 - load the list only at the start
  • ssl_verify either verify or not the SSL certificate validity when working through https://. May be True (verify) or False (not verify) The certificate is verified by default.
  • timeout request timeout (seconds)
  • uri API URI of the connected controller (proto://host:port, without /uc-api/)

Parameters are displayed with lm-cmd command or Logic Manager list_controller_props function, modified with set_controller_prop. Function list_controllers displays the list of all connected controllers.

To remove the connected controller use remove_controller function.

When managing the connected controllers, ID can be either the controller ID only or the full ID in the following format: controller_type/ID (i.e. uc/controller1).

Macro execution queues

Prior to execution, the macros are put into global queue. The macros are executed progressively without waiting for the completion of the previous macro. The queue is used for reporting only and reserved for some internal functions. If a macro is required to block execution of the other ones, one should use lock and unlock macro functions operating similarly to SYS API locking.

The status of the macro in queue is similar to the status of the Universal Controller actions.

Startup and shutdown

To manage LM controller use ./sbin/lm-control script with the following options:

  • start start LM controller
  • stop stop LM controller
  • restart restart LM controller
  • logrotate call after log rotation to restart the logging
  • version display the controller version

The controller startup/shutdown is also performed by ./sbin/eva-control which is configured during the system setup.

SCADA Final Aggregator

SCADA Final Aggregator (SFA) is a subsystem usually installed directly in the host wherein the user interface or third-party applications are installed.

It aggregates all the control and monitoring items, logic macros and decision rules from all connected UC and LM PLC controllers into one place. As a result, the final interface or application doesn’t need to know on which controller the item is present, it does all the function calls directly to SFA. Ids of the items should be always specified in oid form (type:group/id) and be unique in the whole installation.

SCADA Final Aggregator

Example of the controller aggregation with the use of two SFA servers

SFA is set up and controlled with sfa-cmd console application and SFA API. The API doesn’t have a user interface by default, it’s developed specifically for certain installation certain installation using SFA Templates (server-side part) and SFA Framework (client-side part).

All changes of item status, actions, and logs are sent to the notification system. In addition, SFA can function as the notification aggregator e. g. by transferring MQTT messages to other application via HTTP or redirecting them to other MQTT servers. SFA usually has several or all available controllers connected. At least one MQTT server should be installed in the setup to let it work in real time.

Since SFA is a part of EVA platform, its operating principles, settings, and configuration files generally match the other components.

etc/sfa.ini configuration file

sfa.ini - primary configuration file of SFA

[server]
; system name (default - hostname)
; name = eva3-sfa1
; system polldelay
polldelay = 0.01
; pid file (default var/sfa.pid)
pid_file = var/sfa.pid
; log file
log_file = log/sfa.log
; db updates - instant, manual or on_exit
db_update = instant
; db file, default runtime/db/sfa.db
db_file = runtime/db/sfa.db
; user db file, default = db_file
; one user db may be used by multiple controllers at once
;userdb_file = runtime/db/users.db
; keep action history in seconds
keep_action_history = 86400
; keep memory log in seconds
keep_logmem = 86400
; debug mode
debug = no
; create crash dump on critical errors
dump_on_critical = yes
; stop server on critical errors (will be restarted via safe-run)
stop_on_critical = yes
; default timeout
timeout = 5

; exec commands before/after config/db save,
; e.g. mount -o remount,rw / (then back to ro)

;exec_before_save =
;
;exec_after_save =

; default mqtt notifier for updates for new items
;mqtt_update_default = eva_1:2

[sysapi]
; enable remote file management functions
;file_management = yes

[webapi]
; web api listen on IP/port
listen = 0.0.0.0:8828
;ssl_listen = 0.0.0.0:8829
;ssl_module = builtin
;ssl_cert = test.crt
;ssl_key = test.key
;ssl_chain = test.pam
; session timeout
;session_timeout = 3600
; server thread pool
;thread_pool = 15

; uncomment to use frontend X-Real-IP header to get client real IP address
;x_real_ip = yes

runtime/sfa_cvars.json variables file

sfa_cvars.json - file containing user variables. All SFA user variables are directly available in SFA Templates and SFA Framework after login with any valid user or API key.

The file contains a JSON dict:

{
 "VAR1": "value1",
 "VAR2": "value2"
}

Variables can be changed while the server is running via SYS API as well as sfa-cmd cvar get and cvar set commands.

etc/sfa_apikeys.ini API keys file

API access keys are stored into etc/sfa_apikeys.ini file. At least one full access key named masterkey should be present for proper functioning. Important: with master key and API anyone can receive the full access to the system similar to root user (or the user SFA is run under), that is why it is recommended to use this key only in supervisory networks or even restrict its use to local host only.

[masterkey]
; the default master key is eva, change it!
key = eva
master = yes
hosts_allow = 127.0.0.1

;[key1]
;key = key1secret
;groups = group1/#, group2, group3/+/somegroup
;pvt = #
;rpvt = 127.0.0.1/#
;hosts_allow = 0.0.0.0/0
; key will be assigned to requests from these hosts if no key provided
;hosts_assign = 192.168.1.1

;[key2]
;key = key2secret
;items = macro1, lvar1, lvar2
;sysfunc = yes
;allow = cmd, dm_rule_props, dm_rules_list
;pvt = dir1/#, file1, dir2/file2, dir3/+/content.js
;rpvt = 127.0.0.1:8080/file1.png 127.0.0.1:8080/img/#
;hosts_allow = 0.0.0.0/0

Connecting UC and LM PLC controllers

SFA works only with the items known to the controller. Prior to connecting UC and LM PLC remote controllers, it is necessary to connect SFA to MQTT server (via its notification system), to which other controllers will send the events. SFA reads the list of items, macros, rules and the initial item status from the connected controllers; further status updates are performed via MQTT.

For timers to be displayed correctly in the user interface, it is important to maximally synchronize the system time between LM PLC and SFA, if LM PLC controllers are set up on the remote servers.

To connect the controllers you should use sfa-cmd console command or SFA API append_controller function.

When connecting, it is necessary to indicate minimum URI of the connected controller and API KEY functioning either as a master key or the key with access to certain items. If Logic Manager and UC keys are the same, the key can be set as $key (\$key in the command line). In this case, LM will use the local key of its own configuration.

sfa-cmd append_controller -u http://localhost:8812 -a secretkey -y
sfa-cmd append_controller -u http://localhost:8817 -a secretkey -y

You may specify a controller type with -g argument (-g uc or -g lm). If the group is not specified, SFA tries to automatically detect the remote controller type.

Configurations of the connected controllers are stored in the folder runtime/sfa_remote_uc.d/ for UC and runtime/sfa_remote_lm.d/ for LM PLC.

SFA automatically loads the connected controller data (its id) and saves the configuration to runtime/sfa_remote_<type>.d/<ID>.json.

Items from remote controllers are loaded at the SFA start and then refreshed with reload_interval frequency set individually for each connected controller. If SFA fails to get the item list during loading, it will use the existing one.

To control the list of the received items you can use sfa-cmd or SFA API function list_remote:

sfa-cmd list_remote -p unit
sfa-cmd list_remote -p sensor
sfa-cmd list_remote -p lvar

The available logic macros are listed by the command

sfa-cmd list_macros

Note

Macros from system group and its subgroups are not loaded to SFA

All connected controllers have the following properties that can be changed while SFA is running:

  • description optional description of the controller
  • key API key used to access the connected controller
  • mqtt_update MQTT notifier through which items update their status
  • reload_interval interval (seconds) to reload the item list from the server, 0 - load the list only at the start
  • ssl_verify either verify or not the SSL certificate validity when working through https://. May be True (verify) or False (not verify) The certificate is verified by default.
  • timeout request timeout (seconds)
  • uri API URI of the connected controller (proto://host:port, without /uc-api/ or /lm-api/)

Parameters are displayed with sfa-cmd command or SCADA Final Aggregator list_controller_props function, modified with set_controller_prop. Function list_controllers displays the list of all connected controllers.

To remove the connected controller use remove_controller function.

When managing the connected controllers, ID should be always provided in the full format: controller_type/ID (i.e. uc/controller1).

Interface

SFA interface is always specifically designed for a certain installation using SFA Templates, SFA Framework and SFA PVT. Interface files are stored in ui folder, interface is available at http(s)://<IP_address_SFA:Port>/ (redirects to /ui/) or http(s)://<IP_address_SFA:Port>/ui/.

Startup and shutdown

To manage SFA server use ./sbin/sfa-control script with the following options:

  • start start SFA controller
  • stop stop SFA controller
  • restart restart SFA controller
  • logrotate call after log rotation to restart the logging
  • version display the controller version

The controller startup/shutdown is also performed by ./sbin/eva-control which is configured during the system setup.

Control and monitoring items

An item in EVA means any object which can be controlled or monitored. Universal Controller has 2 native item types: unit and sensor, plus multiupdates, which may contain multiple items at once.

Logic Manager has one native item type lvar (logic variable), additionally it loads remote units and sensors from connected UCs. SCADA Final Aggregator has no native item types and loads everything from the connected remote controllers.

The configurations of items are stored in runtime folder, from where the controllers loads .json files. Each item has multiple parameters which may be predefined or customized. All customized parameters can be displayed with the help of API list_props or EVA console tools In case the configuration file settings are changed manually or by 3rd party software, controller should be restarted to reload the configurations.

Common item parameters

  • id item ID, i.e. ‘lamp1’. When using simple layout, must be unique within one controller, even if items are in different groups. This creates some complications when designing the whole installation architecture but allows to keep EVA configuration and item scripts organized in a simple way and makes system administration and support much easier.
  • group item group, i.e. ‘hall/lamps’. Assigned at the time of item creation, the group can’t be changed later to avoid synchronization problems.
  • full_id full item id (i.e. ‘hall/lamps/lamp1’), read-only. Must be unique within one controller despite of layout used.
  • oid object id, unique within the whole installation, same as full_id, but also contains the item type: ‘unit:hall/lamps/lamp1’, read-only
  • description item description
  • virtual boolean (true/false) param which shows if the item is virtual or real.

Note

All EVA functions, commands and parameters can accept oid as the item identifier.

Item layout

EVA ICS allows to use two types of item layouts: simple and enterprise. if the system is not being used yet and there are no items created, layout can be set in Universal Controller and Logic Manager configuration files (option within [server]: layout=<simple|enterprise>). Otherwise you should use sbin/layout-converter tool to convert simple layout to enterprise. Inverse conversion is not possible.

Benefits of simple layout:

  • Good to use in simple installations or in the installations where each component has no similar items. Each item should have its own unique ID, despite that items are located in different groups.
  • When doing controller maintenance tasks, you can address each item by its ID instead of full ID or oid.
  • Item configuration files are named as <ID>.json and can be easily located.

Benefit of enterprise layout: different items in different groups can have the same IDs. Ideal for setups where multiple similar components are managed by one controller.

In general, simple layout should be used only for testing and simple temporary setups. For usage in production environment, enterprise layout is always recommended.

Unit

A unit is a physical item, a device that we control. A unit is not a relay port, a dimmer or a controlled resistor. This is an object, for example: an electric lamp chain, a door, ventilation, a window, a pump or a boiler.

The unit can be controlled with one relay (e.g. a lamp chain: we control the whole chain by turning on/off the relay port) or with several ones (controlling e.g. a garage door often requires two relays: the first one starts the motor, the second one chooses the direction of movement). However, a door is one unit with “open” or “closed” statuses.

All units are connected to Universal Controller subsystems, which control them and form the single “unit” with one or several relays/programmable switches using control scripts. One Universal Controller can work with multiple units, but one unit should be connected to only one Universal Controller in order to avoid conflicts. Nevertheless, for reliability, one unit can be connected to several controllers, if its state is correctly synchronized via MQTT.

Each unit has its unique ID, for example “lamp1”. ID can include numbers, uppercase and lowercase Latin characters and some special characters like minus (-) or dot (.).

Unit parameters are set via configuration. The unit can be either physical or virtual.

Status of the unit state

Status of the unit state is always an integer (a positive number or 0), and is by default 0 - unit is “off” (inactive) and 1 - “on” (active).

A unit can have other statuses: for example, a dimmer can include status 2 - enabled at 10% of the capacity, 3 - enabled at 50% of the capacity, window may be fully open or 50%. In the item configuration, you may assign a label to each status for enhancing its usability in interfaces.

Status -1 indicates that unit has an error status. It is set from the outside or by the system itself if the unit wasn’t updated for more than “expires” (value from item config) seconds.

Value of the unit state

Sometimes it’s not necessary to create multiple new statuses for the unit. In such cases, the unit also has a “value” parameter (which can include both numbers and letters). For instance, a motor can be controlled by two unit statuses - 0 and 1, i.e. turned on/off, but Its speed is set by value. You can also use value to control e.g. dimmers.

EVA does not use unit value for internal control and monitoring logic (except in your custom macros), that is why you can set it to any value or several values separating them with special characters for further processing.

The blank value is “null”. It is not recommended to use “” (blank) value, because such values cannot be transmitted via MQTT correctly. In most cases, the system itself replaces the blank value with “null”.

Units in EVA hive

All units have oids like unit:group/unit_id e.g. unit:light/room1/lamp1

For synchronization via MQTT, the following subjects are used for units

  • [space/]unit/<group>/<unit_id>/status unit status, integer
  • [space/]unit/<group>/<unit_id>/value unit value
  • [space/]unit/<group>/<unit_id>/nstatus new unit status (different from status if action is started), integer
  • [space/]unit/<group>/<unit_id>/nvalue new unit value
  • [space/]unit/<group>/<unit_id>/action_enabled are actions enabled for the unit or not (boolean, True/False)
Unit parameters
  • expires integer value, time (seconds) after which the item state is considered “expired”. If the item state was not updated during this period, the state automatically is set to -1 (error), value is deleted (set to null). If ‘expires’ param is set to 0, this feature is disabled. The minimum expiration step is 0.1 sec.
  • mqtt_update = “notifier:qos” if set, the item may receive active state updates through notification from the specified MQTT server. Example: “eva_1:2”.
  • snmp_trap if set, the item may receive active state updates via SNMP traps.
  • update_exec a script for passive update of the item state, “xc/uc/ITEMID_update” by default.
  • update_interval integer value, time (seconds) interval between the calls for passive update of the item. Set 0 to disable passive updates. Minimum step is 0.1 sec.
  • update_delay integer value, delay (in seconds) before the next call of the passive update, may be used to avoid multiple update scripts of different items run simultaneously.
  • update_timeout integer, value, time (seconds) in which the script of the passive update should finish its work or it will be terminated.
  • action_allow_termination boolean, allow currect running action termination by external request.
  • action_always_exec boolean, always execute the actions, even if the intended status is similar to the current one
  • action_enabled boolean, allow or deny new actions queue/execution
  • action_exec a script which performs the action, “xc/uc/ITEMID” by default.
  • action_queue={0|1|2}
    • 0 action queue is disabled, if the action is running, new actions are not accepted
    • 1 action queue is enabled, all new actions are put in queue and executed in a normal way
    • 2 queue is disabled, new action terminates the current running one and then is executed
  • action_timeout integer, value, time (seconds) in which the script of the action should finish its work or it will be terminated.
  • auto_off integer, the simple automation parameter: the command to turn the unit off (call an action to set status = 0) will be executed after the indicated period of time (in seconds) after the last action performed for this unit. Set 0 to disable this feature. Minimum step is 0.1 sec.
  • location you may specify units’ physical location, as GPS coordinates or in custom format. To specify GPS coordinates, set the parameter to value longitude:latitude or longitude:latitude:altitude. If you choose to set location as GPS or some other coords, full unit state is appended with virtual parameters loc_x, loc_y (and if altitude is specified - loc_z). These virtual parameters are parsed automatically from location and can be used later e.g. to filter units by location or to put units on geographical map.
  • mqtt_control = “notifier:qos” item gets actions through notifications from a specified MQTT server, for example “eva_1:2”, actions should be sent to path/to/unit/control (e.g. unit/hall/lamps/lamp1/control) in a form of text messages “status [value] [priority]”. If you want to skip value, but keep priority, set it to null, i.e. “status 0 null 50”.
  • status_labels “labels” used to display the unit statuses by the interfaces. Labels may be changed via UC API or uc-cmd, in the following way: status:number = label, e.g. “status:0” = “stop”. By default the unit has labels “status:0” = “OFF”, “status:1” = “ON”. Status labels can be used as status param to execute unit actions, in this case controllers check the status match to the specified label (case insensitive).
  • term_kill_interval integer, difference (in seconds) between stopping and forcible stopping the action or update script. Tip: sometimes it is useful to catch SIGTERM in the script to exit it gracefully. Cannot exceed the value of timeout** 2, where timeout** default timeout, set in a controller config.
  • update_exec_after_action boolean, start passive update immediately after the action is completed (to ensure the unit state has been changed correctly)
  • update_if_action boolean, allow or deny passive updates while the action is being executed
  • update_state_after_action boolean, if action is completed successfully, the controller assumes that its actual unit state has been changed correctly and sets it without calling/waiting for the state update.

Sensor

The sensor value is the parameter measured by the sensor: temperature, humidity, pressure etc.

In terms of automation the difference between sensor item and unit item is obvious: we change the unit state by ourselves and monitor it only for the sake of checking the control operations, while the sensor state is changed by the environment.

Regarding the system itself, unit and sensor are similar items: both have status and value, the item status is monitored actively (by UC API, MQTT message, SNMP traps) or passively (by calling the external script).

The sensor can have 3 statuses:

  • 1 sensor is working and collecting data
  • 0 sensor is disabled, the value updates are ignored (this status may be set via API or by the user)
  • -1 sensor error (“expires” timer went off, the status was set because the connection with a physical sensor got lost during passive or active update etc), when the sensor is in this status, its value is not sent via notification system to let other components work with the last valid data.

Note

The sensor error state is automatically cleared if new value data arrives.

Important: the sensor error may be set even if the sensor is disabled. It means that the disabled sensor may be switched to “error” and then to “work” mode by the system itself. Why it works that way? According to the logic of the system, the sensor error is an emergency situation that should affect its status even if it is disabled and requires an immediate attention of the user. If you want the sensor not to respond to external state updates - set it to the virtual state

Sensors (and sometimes units) can be placed on the same detector, controller or bus queried by a single command. EVA can use multiupdates in order to update several items at once.

Since the system does not control, but only monitors the sensor, it can be easily connected to several Universal Controllers at once if the equipment allows making parallel queries of the state or sending active updates to several addresses at once.

Note

The sensor doesn’t set its status to ‘-1’ on expires if its status is 0 (disabled)

Sensors in EVA hive

All sensors have oids like sensor:group/sensor_id e.g. sensor:temp/t1

For synchronization via MQTT, the following subjects are used for units

  • [space/]sensor/<group>/<sensor_id>/status sensor status, integer
  • [space/]sensor/<group>/<sensor_id>/value sensor value
Sensor parameters

Sensors have the same parameters as units, except they don’t have action_*, auto_off, mqtt_control and status_labels.

Logic variable

EVA Logic Manager uses the logic variables (lvars) to make decisions and organize production cycle timers.

The parameters of logic variables are set in their configurations.

Actually lvars are similar to sensors, but with the following differences:

  • The system architecture implies that the sensor value is changed depending on the environment; the logic variables are set by the user or the system itself.
  • The logic variables, as well as the sensors, have statuses -1, 0 and 1. However, if the status is 0 (variable is disabled) it stops responding to any value-only changes.
  • The logic variables exchange two more parameters with the notification system: “expires” (time in seconds after the variable is set, and then takes the null value and -1 status) and set_time - time when the value was set for the last time.

The same logic variable may be declared on several logic controllers, but the “expires” configuration value should remain the same because each controller processes it autonomously. The variable becomes “expired” once it is declared as such by any controller.

Note

LVar doesn’t set its status to ‘-1’ on expires if its status is 0 (disabled)

The logic variable values may be synchronized via MQTT server or set via API or external scripts - similar to sensors.

You can use several logic variables as timers in order to organize production cycles. For example, there are three cycles: the pump No.1 operates in the first one, the pump No. 2 in the second one, and both pumps are disabled in the third one. In order to organize such cycle, let us create three variables: cycle1, cycle2, cycle_stop with “expires” values equal to the duration of each cycle in seconds.

Then - in the decision-making matrix you should specify the rules and macros run as soon as each cycle is finished. The macros run and stop the pumps as well as reset the timer variables of the next cycle: as soon as cycle_stop is finished, the pump No.1 is run, the cycle1 timer variable is reset; as soon as the cycle1 is finished, the pump No. 2 is run and cycle2 variable is reset; as soon as cycle2 is finished, both pumps are disabled and cycle_stop is reset.

In order to synchronize timer values with interfaces and the third-party applications, use LM API test command that displays the system information, including local time on the server on which the controller is installed.

However, when used in industrial configurations, it is recommended to synchronize time on all computers without any additional software hotfixes.

LVars in EVA hive

All logic variables have oids like lvar:group/lvar_id e.g. lvar:service/var1

For synchronization via MQTT, the following subjects are used for units

  • [space/]lvar/<group>/<lvar_id>/status lvar status, integer
  • [space/]lvar/<group>/<lvar_id>/value lvar value
  • [space/]lvar/<group>/<lvar_id>/set_time last set time (unix timestamp)
  • [space/]lvar/<group>/<lvar_id>/expires value expiration time (seconds)
LVar parameters

As LVars behavior is similar to sensors except the values are set by user/system, they have the same parameters, except lvars can’t be updated via SNMP traps and can’t be virtual (lvar is actually virtual by default).

Examples using LVars

You may use lvar as a

  • Variable To use lvar as a shared variable to exchange some information between controllers, apps and SCADA interfaces, just set its value (and status if you want) and that’s it.
  • Timer
    • Set expires configuration param
    • Use reset to set lvar status/value to 1 and reset the expiration timer
    • Use clear to set lvar status to 0 and stop it reacting to expiration (when used with lvar which have expires param set, clear changes its status instead of value)
    • Use decision rules with the conditions on_set and on_expire to run the macros when the timer is set/expired
    • if the timer has status set to 1, it’s running
    • if status is 0, it’s disabled with clear function
    • if status is -1 and value is null (empty), the timer is expired
  • Flag
    • Use lvar as a simple boolean variable to exchange the information True/False, yes/no, enabled/disabled etc.
    • Use reset to set lvar value to 1 which should be considered as True
    • Use clear to set lvar value to 0 which should be considered as False
    • Use toggle to toggle lvar value between 0 and 1
    • Use constructions like if value(‘lvar_id’): in macros to determine is the ‘flag’ lvar is set or not.

Multiupdates

Multiupdates allow Universal Controller updating the state of several items with the use of one script. This could be reasonable in case all items are placed on the same bus or external controller and queried by a single command.

Multiupdate is an independent item in the system with its own configuration and without status and value. In turn, it updates statuses of the included items. Multiupdate can be virtual.

Multiupdates in EVA hive

All multiupdates have oids like mu:group/mu_id e.g. mu:environment/mu1

Multiupdates don’t have their own state, so they are not synchronized between servers.

Multiupdate parameters

Multiupdates have the same parameters as sensors, except that “expires”, “mqtt_update” and “snmp_trap”, plus some additional:

  • items = item1, item2, item3… - the list of items for updating, may be changed via UC API and uc-cmd as follows:

    • -p “item+” -v “item_id” add item for update
    • -p “item-” -v “item_id” delete item
    • -p “items” -v “item1,item2,item3…” replace the whole list
  • update_allow_check - boolean, the multiupdate will be performed only in case the passive state updates are currently allowed for all included items (i.e. if some of them run actions at this moment and have update_if_action=False, multiupdate will be not executed)

Device

Multiple cvars, units, sensors and multiupdates can be merged in logical groups called devices. It’s completely up to you how to merge items into device, but it’s recommended to keep them in one or several separate item groups.

Device templates are stored in runtime/tpl folder in JSON format.

You can use uc-tpl command line tool to create device templates using the existing items and uc-cmd or device management UC API functions to create, update and destroy devices.

Device management requires master key or a key with allow=device permission.

Device example

Let’s imagine we have some hardware device, which has 1 relay and 2 sensors. We have a lot of devices like this and we want to create them using template.

Create one instance of device in Universal Controller defining all its items:

  • sensor:device1/device1.sensor1
  • sensor:device1/device1.sensor2
  • unit:device1/device1.relay1

Configure all defined items, then run:

uc-tpl generate -i sensor:device1/device1.sensor1,sensor:device1/device1.sensor2,unit:device1/device1.relay1

This will output device JSON template. Use -t param to output template to file or copy/paste it from the screen. You can use -c param to ask the tool automatically prepare template variables, but in our example it will just replace all 1 to {{ ID }}. We don’t want it to be done this way because we have sensor1 and relay1 items, so let’s edit the template manually:

{
    "sensors": [
        {
            "group": "device{{ ID }}",
            "id": "device{{ ID }}.sensor1"
        },
        {
            "group": "device{{ ID }}",
            "id": "device{{ ID }}.sensor2"
        }
    ],
    "units": [
        {
            "group": "device{{ ID }}",
            "id": "device{{ ID }}.relay1"
        }
    ]
}

(template will also contain items’ configurations which are omitted in the example)

Save the final template as runtime/tpl/mydevice.json folder, and then

# execute this command to create new device "device5"
uc-cmd device create mydevice -C ID=5 -y
# execute this command to destroy "device5"
uc-cmd device destroy mydevice -C ID=5

Configurations of the newly created items of device5 are exact copies of the items of device1. The only configuration difference is the params where we’ve put template variables instead of part or full param value (in our example: {{ ID }}).

Note

Device templates are actually jinja2 templates, so you can use any jinja2 syntax in them (loops, conditions and etc.)

Device limitations

Drivers

Universal Controller uses 2 ways for controlling and monitoring items: item scripts and drivers. Drivers are the most advanced way, they are faster, contain all process logic and can be used to work with devices on a single bus, providing locking/unlocking mechanisms.

Code of driver is executed directly inside the controller core and unlike script can’t be terminated, unless it gets termination signal and decides to stop. If driver process causes timeout and there is no way to stop it, controller raises a critical exception and may terminate itself.

You should always use only reliable and tested drivers, otherwise your system may become unstable.

Structure

Each driver contains 2 modules: LPI (logical to physical interface) and PHI (physical interface). To load new driver into controller, follow the steps:

List the available PHI mods:

uc-cmd phi mods

Get PHI module information:

uc-cmd phi modinfo <phi_module>

If the desired PHI is not listed, download it and put to xc/drivers/phi folder. Official PHI modules are available at https://www.eva-ics.com/phi. You may either download the module manually or use

uc-cmd phi download <phi_module_uri>

command. Note that UC host doesn’t need to have a direct connection to the host you download PHI from, module is downloaded first to the host where uc-cmd is started, verified and then automatically uploaded to the controller.

Execute the command to list PHI configuration variables:

uc-cmd phi modhelp <phi_module> cfg

This will display the configuration variables, used when PHI is loaded (port numbers, default values etc.). Variables marked required=True should be always defined otherwise controller will fail to load PHI.

Load PHI with the following command:

uc-cmd phi load <phi_id> <phi_module> [-c config] [-y]
# example
uc-cmd phi load v1 vrtrelay -c default_status=1,update=5 -y

Param -y is used to ask the controller to save driver configuration right after PHI is loaded.

After the successful load, PHI will automatically create the most suitable driver for itself, called <phi_id>.default. This usually provides basic driver logic and doesn’t mean the driver is suitable for your task. You may replace it’s LPI module or define a different driver with another LPI.

To assign driver to the specified item, use the command:

uc-cmd driver set <item_id> <driver_id> [-c config] [-y]
# example, set test_lamp to 5th relay port of driver v1
uc-cmd driver set unit:lamps/test_lamp v1.default -c port=5

Param -y is used to ask the controller to save item configuration right after driver is assigned.

Param -c is used to set driver configuration for the specified item: set port, logic etc.

Advanced usage: EVA item can have different drivers or scripts for actions and updates. To assign different drivers, modify item properties action_exec, update_exec, action_driver_config and update_driver_config (e.g. with uc-cmd config props). Driver is assigned to the property with |driver_id value, e.g. |v1.default.

Note

All custom-defined user variables are always passed to driver function calls, which allows to set some device-specific or logic-specific options as global or for the particular item group.

Drivers and actions

How the driver handles action commands

Note that params started with _ are passed to PHI calls directly (without _ prefix), this allows specifying different hosts, bus addresses (if PHI is developed as “universal”) without a need to load different drivers for each item.

Drivers and updates

How the driver handles update commands

Use commands uc-cmd phi unload and uc-cmd phi unlink to unload and unlink unnecessary PHI modules, but note that driver and PHI can’t be unloaded while they’re assigned to items. You must first assign a different driver to the item or use uc-cmd driver unset command.

You can load PHIs/drivers with the same IDs even if they are already present in the system without unloading them first. In this case, new modules/configuration replace the old ones.

Logical to physical interfaces (LPI)

LPI module handles the whole driver logic and doesn’t contain any code, specific for the equipment. All it needs is to process the logic and call the assigned PHI.

When the controller loads new PHI, it creates a driver called <phi_id>.default, assigning LPI to provide basic functionality, but you may want to replace it or use different logic for different items.

To list available LPI mods, use the command:

uc-cmd lpi mods

To get module information, use the command:

uc-cmd lpi modinfo <lpi_module>

Currently we don’t provide any additional LPI modules or SDK, all available mods are included in EVA ICS distribution.

To get additional module info, use the following commands:

# list module configuration options
uc-cmd lpi modhelp <lpi_module> cfg

# list module options used when action is called
uc-cmd lpi modhelp <lpi_module> action

# list module options used when state update is called
uc-cmd lpi modhelp <lpi_module> update

Configuration options are used when you load a driver (e.g. to modify LPI default behavior), separated with commas.

Action and update options are used when you assign a driver to the specified item; separate them with commas. Options marked required=True should be always defined.

Let’s see what modules are available.

basic LPI

Basic status on/off LPI module, used to control simple devices which have only status 0 (OFF) and 1 (ON), i.e. lamps, relay ports (directly) etc.

Used in default drivers for relay, sockets and similar PHIs, doesn’t need to be configured when loaded.

When assigning driver containing basic LPI mod to the specified item (uc-cmd driver set), the assign configuration should contain port number (-c port=N) which usually matches the physical relay port.

Port number can be specified as a list (-c port=N1|N2|N3), in this case all listed ports will be used in commands.

Note

If relay port number is specified as i:N e.g. i:2, LPI commands will consider it is inverted meaning 0 is for on and 1 is for off. This works both for basic as well as for any other relay control LPI.

sensor LPI

Basic sensor monitiring, used to get data from specified sensors.

Used in default drivers for sensors, doesn’t need to be configured when loaded.

LPI doesn’t provide action functionality. When assigning driver containing sensor LPI mod to the specified item (uc-cmd driver set), the assign configuration should contain port or bus address number.

ssp LPI

Similar to sensor LPI, but doesn’t contain any options at all. Used when PHI can work only with one physical equipment (e.g. sensor with TCP/IP API) and all equipment options are already set in PHI.

esensor LPI

Sensor monitoring with advanced functions. Can monitor physical sensor groups returning average, maximum or minimum value. Can ignore sensor values if they seem to be invalid in case one or several sensor in a group fail (while there are enough working sensors in a group).

Configuration options (set with uc-cmd driver load):

  • skip_err If True, failed physical sensor in a group will be skipped, otherwise EVA sensor item gets error value.
  • gpf Group port function, get values from the sensors in a group, then return:
    • avg average value
    • max maximum value
    • min minimum value
    • first first available value from any working physical sensor
  • max_diff maximum value difference until the sensor in a group is marked as failed and its value is ignored. E.g.: set this option 10 and let it poll the temperature sensors group. All sensors with temperature difference 10 degrees or more from the average are ignored.

Update options (set with uc-cmd driver set):

  • port driver port or ports (array). If you use multiple ports (group), they should be separated with pipes (|) for the items. Group separation for EVA multiupdate items should be made with double pipes (||)
  • any configuration option (optional). E.g. if gpf=avg is defined, it overwrites default LPI behavior for the specified item.
multistep LPI

Module used for such common tasks as door or window opening. To use this module you must connect your equipment to 2 relay ports: one will give power to motors, the second will set the direction.

Configuration options (set with uc-cmd driver load):

  • bose (break on state error). The module requires to know the current door or window position is. If you set this option to True and the current item status is error, the action will be not executed. Otherwise LPI will pass and consider the item status is 0.

Action options (set with uc-cmd driver set):

  • port contains one or several (separated with |) relay ports used to power a motor.
  • dport contains one or several (separated with |) relay ports used to set a direction.
  • steps list of float numbers, contains time (in seconds) of power access period to the motor to reach the next step. E.g. you have a door with 3 positions: closed, half-open and completely open. steps option will contain 2 numbers (e.g. 20|25) which tells LPI the door state from 0 to 1 is changed by running motor for 20 seconds, the state from 1 to 2 is changed by running motor for 25 seconds, so LPI can automatically calculate the full opening/closing cycle is 45 seconds.
  • warmup float number (seconds). LPI will add this value to the time for running the motor if the state is neither fully open nor fully closed, to let it “warm up” before doing actual work.
  • tuning float number (seconds). LPI will add this value to the time, if action is open full or close full to make sure the door is fully open/closed.
  • ts (to-start) number which indicates the following: e.g. you have a door with status from 0 (fully closed) to 5 (fully open) and defined the middle states with steps. But when calling action “set this door to 2” you can’t be sure the door position is equal when setting it from fully open and fully closed. But if you set e.g. ts=2 and the current status is greater than 2, it will tell LPi firstly to completely close the door (go to status=0) and then go to status=2.
  • te (to-end) same as ts but in an opposite way: set the status number, starting from which the door will be fully open first, then go to the desired status.

Note

LPI will completely refuse to run the action if it calculates that therese is not enough time to complete it. Set item action_timeout to the proper value.

Update options:

The module doesn’t provide any state update functionality. If you want to sync door/window item states with real, use separate reed switch sensor.

Loading driver with the chosen LPI

Firstly, you can list available LPIs with the command:

uc-cmd lpi mods

Consider the desired PHI is already loaded. To load the driver and combine PHI+LPI, use the command:

uc-cmd driver load <phi_id>.<lpi_id> <lpi_module> [-c config] [-y]
# in example, for PHI loaded as "v1":
uc-cmd driver load v1.ms multistep -c bose=true -y

Physical interfaces (PHI)

PHIs are modules, which contain no data processing logic but code to work directly with hardware equipment.

We provide a basic set of PHIs for the popular automation equipment (at https://www.eva-ics.com/phi), but if your equipment isn’t supported, it’s not so hard to develop your own PHI.

We’ve already described how to get and load PHIs, here is some additional important information.

Universal PHIs

If the word “universal” is listed in PHI features, it means the module can be loaded once and provide interface for all supported equipment. E.g. let’s take a look on sr201 PHI module which provides support for SR-201 compatible relays:

# get PHI module info
uc-cmd phi modinfo sr201

# get PHI configuration help
uc-cmd phi modhelp sr201 cfg

# get PHI options for obtaining the data
uc-cmd phi modhelp sr201 get

# get PHI options for setting the data
uc-cmd phi modhelp sr201 set

All of cfg, get and set have an option host which should be defined ether in PHI configutation (uc-cmd phi load with host config option or in item driver configuration (uc-cmd driver set with _host config option). Setting different host option value in item driver configuration lets one sr201 PHI manage all available SR-201 relays.

Physical events

If the word “events” is listed in PHI features, it means the module can handle hardware events e.g. react to the alarm sensors or update item state when an external event is received.

Drivers and events

How the driver handles physical events

In practice, it means PHI provides data, obtained from the hardware, to controller and asks it to update all items using drivers which contain PHI module which have an event.

When doing update, drivers LPI modules don’t ask PHI to get hardware data working only with data already provided by the hardware.

Drivers and multi updates

If the word “aao_get” is listed in PHI features, it means you don’t need to create multiupdates in Universal Controller to update several items at once. “aao_get” (all-at-once-get) means PHI can obtain all hardware data itself and then ask the controller to update all items using drivers which contain PHI equally to updating on physical events.

How to use this feature: All PHIs with “aao_get” feature also have configuration param named update which means how frequently (in seconds) PHI should collect data from the equipment and initiate item updates. update value should be defined in PHI load config and be greater than zero.

Example:

uc-cmd phi load relay2 sr201 -c host=192.168.20.2,update=5 -y

As soon as the driver is assigned to item (uc-cmd driver set), it starts getting state updates every 5 seconds.

Testing PHIs and additional PHI commands

As soon as PHI is loaded, you can test how it works. All PHI modules respond to the command:

uc-cmd phi test <phi_id> self

which returns result “OK” or “FAILED”.

PHI can provide additional testing; to get a list of testing commands, execute:

uc-cmd phi test <phi_id> help

Some PHIs can provide additional commands to set up or control the hardware equipment. To get a list of these commands, execute:

uc-cmd phi exec <phi_id> help

Example: PHI module dae_ro16_modbus has a command to change ModBus unit ID of the hardware equipment. Let’s change unit ID to 5:

uc-cmd phi exec <phi_id> id 5

The module will flash new unit ID into hardware and change unit ID in self configuration. Don’t forget to restart the hardware to let it be accessed with new unit ID and save PHI config (uc-cmd save).

Item scripts

Item scripts are used to update items’ state and execute actions. Scripts are placed in xc folder (xc/uc for Universal Controller, xc/lm for Logic Manager) and may be either written in any scripting language or be binary executables. The script file should have exec permissions.

Universal Controller has 2 ways for controlling and monitoring items: drivers and item scripts. Sometimes item scripts are harder to implement as you must define all logic by yourself, as well as implement hardware calls, they are also slower because controller needs to execute an external process. But item scripts are more reliable than drivers because the external process can be easily terminated/killed by timeout, so if you don’t have a driver for your equipment or the driver is unstable, it is a good idea to use scripts.

All the examples provided in this documentation are written in the classic Bourne shell (bash). It is recommended to use dash or perl in heavy loaded production systems to provide better startup speed. Experience has shown that the modern systems do not require the use of the lower-level languages compiled into executable files: it complicates integration and servicing; on the other hand, the difference in program operation is only a few milliseconds.

The script always has max execution time (timeout) specified in item configuration (or default controller timeout). After that the system terminates the script operation: firstly - voluntary, by sending SIGTERM, then - forcibly by sending SIGKILL (this in-between time may be changed in item configuration with param term_kill_interval)

Script or program always gets the environment variables:

  • all variables from the controller var file (uc_cvars or lm_cvars)
  • PATH variable with additional EVA bin and xbin subfolders
  • EVA_ITEM_ID item ID which the script is executed for
  • EVA_ITEM_OID item OID (type:group/id)
  • EVA_ITEM_TYPE item type: unit, sensor or lvar, (lvars can also be updated with scripts)
  • EVA_ITEM_GROUP full item group
  • EVA_ITEM_PARENT_GROUP the nearest parent group of the item (e.g. building1/env/room1/temp1 - room1)
  • EVA_ITEM_FULL_ID full item ID
  • EVA_ITEM_STATUS current item status
  • EVA_ITEM_VALUE current item state value

The system considers the script to be successful if its exit code is 0.

Item actions

Item actions are used to control the units. After the unit action has been called, the controller executes the appropriate script. By default, control scripts are placed in xc/uc/ folder and named ID, where ID is a unit ID, for example, xc/uc/lamp1. This may be changed in the item configuration to let e.g. one script execute actions for a group of units.

The startup parameters of the action script include:

  • param1 unit ID
  • param2 new unit status
  • param3 new unit value

A simple example script: send toe command to X10 controller via mochad

#!/bin/sh
[ $2 -eq 0 ] && CMD="off" || CMD="on"
echo "pl $1 $CMD" | nc localhost 1099

Such script is actually universal for X10 (in case units are named according to X10 - a1-aX)

Another example: activate the third EG-PM2-LAN plug. Here we access EG1_IP variable from uc_cvars

#!/bin/sh

EG-PM2-LAN $EG1_IP 3 $2

Another example: control the relay (4 modules, 1 relay block) by Denkovi AE

#!/bin/sh

${RELAY1_CMD}.1.4.0 i $2

where in uc_cvars:

RELAY1_CMD = snmpset -v1 -c private RELAY_IP_ADDRESS .1.3.6.1.4.1.19865.1.2

In the previous examples, we used the same command for turning the units on/off. Let us review a more complex logic. The next example shows how EVA can shut down the remote server machine and turn it on via Wake on LAN (tip: such script requires more action_timeout in unit config):

#!/bin/sh

case $2 in
0)
            ssh eva@${SERVER_IP} "sudo /sbin/poweroff"
   ;;
1)
   wakeonlan ${SERVER_MAC}
   ;;
esac

In the queue history script is marked as completed if it completed independently with 0 code, failed - if the code differs from 0.

The script or program can display anything on stdout/stderr. This data, as well as the exit code, will be recorded in “out” and “err” fields of the result dict.

Sometimes it is useful to catch SIGTERM in the script/program, e.g. if you operate a motor that must be stopped after the script gets a termination signal. Warning:, the system does not track/stop child processes executed after SIGTERM is sent to the script.

Passive updates of item state

Passive updates are used to collect the state of the equipment which doesn’t report its state by itself. By default, scripts for passive updating of item state are named ID_update, where ID is a item ID, for example: lamp1_update.

The status update script is executed:

  • Every X seconds, if update_interval specified in the config is more than 0
  • After the unit action succeeds (if update_exec_after_action=true in config)

The system considers the script was executed successfully if its exit code is 0, otherwise, its new item state is ignored.

Passive update scripts get the following parameters:

  • param1 “update”
  • param2 item ID

Script should print on stdout only the new status and (optionally) value, separated by space, e.g.

0 NEW_VALUE

For the sensor, its data should be printed as:

1 VALUE

where 1 means the sensor is working properly.

Let us analyze an example of a simple script, e. g. state update of the sensor that monitors the remote machine

#!/bin/sh

ping -W1 -c1 ${SERVER_IP} > /dev/null 2>&1 && echo "1 1"||echo "1 0"

Unit status - the third EG-PM2-LAN plug

#!/bin/sh

EG-PM2-LAN evacc-rl5|cut -d, -f3

Update state of the relay (4 modules, 1 relay block) by Denkovi AE

#!/bin/sh

${RELAY1_UPDATE_CMD}.2.0|awk -F\  '{ print $4 }'

where in uc_cvars:

RELAY1_UPDATE_CMD = snmpget -v2c -c public RELAY_IP_ADDRESS .1.3.6.1.4.1.42505.6.2.3.1.3
Multiupdate scripts

Multiupdates allow updating the state of several items with one script which works like a normal passive update script and outputs the states of the monitored items line-by-line:

item1_status item1_value
item2_status item2_value
.....

The order of the output should correspond to the order of the items in the multiupdate.

By default, multiupdate scripts are named ID_update, where ID is a multiupdate ID, for example, xc/uc/temperatures_update for mu ID = temperatures.

For example, let’s update all 8 units connected to the relay controlled by DS2408

#!/bin/sh

w1_ds2408 28-999999999999 || exit 1

The script output will be as approximately follows:

1
0
1
1
1
1
0
1

where each row contains the status of the unit connected to the corresponding relay port.

Commands

Commands are used if you need to run some commands remotely on the server where EVA controller is installed. Commands are executed with controller cli tools, with SYS API function :ref:cmd<cmd> or with macro function.

For command scripts:

  • Configurations are absent. Scripts are named as xc/cmd/SCRIPT_NAME
  • Script timeout is set when it is started

Example of a command usage: a speaker is connected to a remote machine. We want to play some sound as an additional feedback after the certain macros or actions are executed

xc/cmd/play_snd

#!/bin/sh

GAIN=-7

killall play > /dev/null 2>&1 && killall -9 play > /dev/null 2>&1
play /data/snd/$1.wav gain ${GAIN}

when you call the command, the sound file_name will be played. If you want to wait until the playback is over add w=15 to API call i.e. to wait 15 seconds before continuing.

ModBus

Universal Controller provides native support of ModBus protocol for ModBus physical interfaces (PHIs). Core support is provided with pymodbus Python module, but with additional functionality, such as bus locking, automatic retry attempts, virtual ports for drivers etc.

Universal Controller works as ModBus master, connection links to all slave devices should be defined as virtual ports. After that, defined virtual ports and ModBus unit IDs should be set in corresponding PHI modules load configurations.

Defining ModBus virtual port

Before using any ModBus PHI, you must define ModBus virtual port. ModBus PHIs work with ModBus virtual ports only, while UC handles all hardware calls and responses.

List of the defined ModBus virtual ports can be obtained with command:

uc-cmd modbus list

To create new ModBus virtual port, execute the following command:

uc-cmd modbus create [-l] [-t SEC] [-r RETRIES] [-d SEC] [-y] ID PARAMS

where:

  • -l lock port on operations, which means to wait while ModBus port is used by another controller thread (driver command)
  • -t SEC ModBus operations timeout (in seconds, default: default timeout)
  • -r RETRIES retry attempts for each operation (default: no retries)
  • -d SEC delay between virtual port operations (default: 20ms)
  • -y save ModBus port config after creation
  • ID virtual port ID which will be used later in PHI configurations
  • PARAMS ModBus params

ModBus params should contain the configuration of hardware ModBus port. The following hardware port types are supported:

  • tcp , udp ModBus protocol implementations for TCP/IP networks. The params should be specified as: <protocol>:<host>[:port], e.g. tcp:192.168.11.11:502
  • rtu, ascii, binary ModBus protocol implementations for the local bus connected with USB or serial port. The params should be specified as: <protocol>:<device>:<speed>:<data>:<parity>:<stop> e.g. rtu:/dev/ttyS0:9600:8:E:1

As soon as the port is created, it can be used by PHI. Let’s create ModBus TCP port and load dae_ro16_modbus PHI module:

uc-cmd modbus create p1 tcp:192.168.11.11:502 -y
uc-cmd phi load r1 dae_ro16_modbus -c port=p1,unit=1 -y

As the result, controller creates a driver r1.default which can be set to item to work with any relay port of unit #1 of the ModBus relay 192.168.11.11 connected via TCP.

Warning

UC will grant ModBus port access to PHI only if it has enough timeout to wait for the longest possible call. It means operation timeout (action_timeout, update_timeout) in item should be greater than modbus_port_timeout*(1+modbus_port_retries). If the command max timeout is less than this value, attempts to access ModBus virtual port return an error.

If you need to change ModBus port params or options, you can always create new ModBus virtual port with the same ID, without deleting the previous one. Port configuration and options will be overwritten.

Testing ModBus virtual port

To test defined ModBus virtual port, execute the following command:

uc-cmd modbus test <ID>
# e.g.
uc-cmd modbus test p1

The command connects UC to ModBus port and checks the operation status.

Note

As ModBus UDP doesn’t require a port to be connected, test command always return “OK” result.

Deleting ModBus virtual port

To delete ModBus virtual port, execute the command:

uc-cmd modbus destroy <ID>
# e.g.
uc-cmd modbus destroy p1

Note that controller doesn’t check if the port is in use or not, so double check this manually before deleting it.

SNMP traps

Active item updates can be done using SNMP traps notifications.

Usually each automation device sends SNMP traps in its specific format and the data should be parsed individually for subsequent processing. You can use a third-party server to receive traps, for example, snmptrapd or trap handler included in EVA Universal Controller.

Built-in trap handler should be enabled in UC configuration file. Usually, SNMP traps server listen on the port 162. The embedded SNMP traps handler can work with SNMP v1 and SNMP v2c protocols.

Both units and sensors can update their state through SNMP traps processing. After the item configuration param snmp_trap is set up, it automatically subscribes to the incoming notifications and accept only the relevant ones.

Currently EVA works with SNMP OIDs only - all snmp variables should be created in this format. To change snmp_trap variable and its child elements you may use uc-cmd console app or UC API set_prop function. In this tutorial we’ll configure SNMP-traps handler with uc-cmd.

ident_vars - identifying the trap

snmp_trap.ident_vars variable is used by the handler to filter trap notifications and parse only those ones directly related to the item. You should use it if, for example, the source sends the state change notifications with the same OID for different items, but the trap contains some tokens or item IDs identifying that the notification is addressed to the particular item. You can set several ident vars (separated by a comma) at once. The notification will be processed only if all ident_vars match the trap.

Example:

uc-cmd set_prop -i unit1 -p snmp_trap.ident_vars -v 1.3.6.1.4.1.3856.1.7.11.0=14,1.3.6.1.4.1.3856.1.7.11.1=U1

Result:

uc-cmd list_props -i unit1
{
"snmp_trap": {
       "ident_vars": {
           "1.3.6.1.4.1.3856.1.7.11.0": "14",
           "1.3.6.1.4.1.3856.1.7.11.0": "U1"
       },
    }
}

To reset ident_vars variable, run the command without -v key.

set_down - handling the failures

When the controller receives trap notification indicating that the item is not available or disabled, its status is set to -1.

This can be made with set_down variable, which’s set similarly to ident_vars. If there are several OID, they should be listed and separated by commas when setting up. The handler assigns an error status to the item only if all set_down variables match the trap.

Example:

uc-cmd set_prop -i unit1 -p snmp_trap.set_down -v 1.3.6.1.4.1.3855.1.7.9.0=7

Result:

uc-cmd list_props -i unit1
{
"snmp_trap": {
   "set_down": {
       "1.3.6.1.4.1.3855.1.7.9.0": "7"
   }
}

To reset set_down variable, run the command without -v key.

set_status - setting the item status

If the source device sends trap notifications with variable having the item status in the format similar to EVA, the handler can immediately change the status to the assigned one. Each item can have only one set_status variable containing OID where item status is being set in a trap.

Example:

uc-cmd set_prop -i unit1 -p snmp_trap.set_status -v 1.3.6.1.4.1.3855.1.7.17.1

Result:

uc-cmd list_props -i unit1
{
"snmp_trap": {
   "set_status": "1.3.6.1.4.1.3855.1.7.17.1"
   }
}

To reset set_status variable, run the command without -v key.

set_value - setting the item value

If the source device sends trap notifications with the variable having the item value (usually, these are various sensor controllers which e.g. send current temperature every minute), the handler can immediately change the value to the assigned one. Each item can have only one set_value variable containing OID where item value is set in a trap.

Example:

uc-cmd set_prop -i unit1 -p snmp_trap.set_value -v 1.3.6.1.4.1.3855.1.7.17.2

Result:

uc-cmd list_props -i unit1
{
"snmp_trap": {
   "set_value": "1.3.6.1.4.1.3855.1.7.17.2"
   }
}

To reset set_value variable, run the command without -v key.

set_if - conditional state updates

If the received trap notification contains certain variables but none of them can be used to set status and/or value as-is, you can define your own rules and set the item status/value according to them.

This operates similarly to set_down, the only difference is that set_down sets the item status to -1, while set_if allows you to set the status and/or value on your own.

The variable is set as follows:

status,value:OID=val1,OID2=val2,OID3=val3

If you don’t need to set status or value, set it to null when defining.

For example, let’s add two conditions:

uc-cmd set_prop -i unit1 -p snmp_trap.set_if -v 1,null:1.3.6.1.4.1.3855.1.7.1.0=4
uc-cmd set_prop -i unit1 -p snmp_trap.set_if -v null,10:1.3.6.1.4.1.3855.1.7.1.0=2

Result:

uc-cmd list_props -i unit1
{
"snmp_trap": {
    "set_if": [
        {
               "value": "10",
               "vars": {
                   "1.3.6.1.4.1.3855.1.7.1.0": "2"
                }
        },
        {
            "status": 1,
            "vars": {
                "1.3.6.1.4.1.3855.1.7.1.0": "4"
            }
        }]
    }
}

When the controller receives a trap with OID 1.3.6.1.4.1.3855.1.7.1.0=2, the value of the item is set to 10. When OID 1.3.6.1.4.1.3855.1.7.1.0=4, the status is set to 1.

One item can have multiple set_if conditions but they can only be added. You can delete the condition only by deleting the entire set_if variable by running the command without -v key.

Disabling SNMP traps processing

To disable SNMP traps processing for a single item, delete its snmp_traps variable:

uc-cmd set_prop -i unit1 -p snmp_trap

Virtual items

Universal Controller items may be either virtual or real. You may toggle the item by changing configuration while the server is running.

Virtual drivers

If you want to build a virtual setup, the best idea is to use virtual drivers . EVA ICS distribution includes 2 virtual drivers which cover all typical needs:

  • vrtrelay Virtual relay driver
  • vrtsensors Virtual sensor pool driver

Both drivers work like the real ones so it’s not necessary to set the item to virtual. When using virtual drivers, set item option virtual=false.

Classic virtual items

Warning

Classic virtual items are deprecated and will be removed in future releases of EVA ICS.

What are virtual items?

Virtual items were originally developed for testing EVA, but we decided to include them in the final product. Virtual items allow you to:

  • Test EVA without connecting real equipment
  • Debug various configurations before embedding
  • Check how the system responds to the emergencies

Units, sensors and multiupdates can be virtual and have the following features:

  • The virtual unit runs the action commands just like the real one
  • You can manually assign any value to the virtual sensor
  • The virtual multiupdate updates states of the several virtual items at once in passive mode

The Command line interfaces console application ./xc/evirtual is used to control virtual items. API and the interface for the remote control of the items will be added to the future EVA versions.

Before switching the item to the virtual state, you should create its configuration:

./xc/evirtual sensor temp1 init ./xc/evirtual sensor temp2 init ./xc/evirtual mu mutemp init temp1,temp2 ./xc/evirtual sensor temp1 set 1 29.44 ./xc/evirtual sensor temp2 set 1 29.556 ./xc/evirtual sensor temp1 update 29.445 ./xc/evirtual mu mutemp update 29.44 29.556

The behavior of evirtual application is similar to the behavior of item scripts; if item is in the virtual state, the system automatically runs “evirtual” instead of the real control/update script. If there is no virtual item configuration, UC will return an error once you try to start the action or the passive update.

After the item is made real, its virtual configuration is preserved and can be used later if the item is made virtual again.

Basic operations with virtual items

To display all virtual configurations, you should run a command

./xc/evirtual list

To create a new configuration, you should run a command

./xc/evirtual unit unit1 init 0 ./xc/evirtual sensor sensor1 init 1 29.445

You can specify the initial value of the item state (status and value) after init. If the virtual configuration already exists, it will be rewritten.

To create a new configuration of multiupdate, you should run a command

./xc/evirtual mu multiupdate1 init unit1,sensor1

To add/delete the item to/from the multiupdate, you should re-create its configuration.

To display the virtual item parameters, you should run the following command:

./xc/evirtual unit unit1

To set status or value of a unit or sensor, you should run the following command:

./xc/evirtual unit unit1 set 1 ./xc/evirtual sensor sensor1 set 1 29.4445

You should always set status, but value is an optional parameter.

To simulate execution of the unit action script, you should run the following command:

./xc/evirtual unit unit1 1

To delete the virtual item configuration, you should run the following command:

./xc/evirtual unit unit1 rm ./xc/evirtual sensor sensor1 rm ./xc/evirtual mu multiupdate1 rm
Active virtual items

Active virtual items automatically send their state to the Universal Controller after being changed via UC API.

In order to make the item active, you should run the following command:

./xc/evirtual unit unit1 x ./xc/evirtual sensor sensor1 x

After running the command

./xc/evirtual unit unit1 nx

the item is no longer active and automatically stops sending its status.

Errors and delays simulation

Simulation of action failures for the unit may be set up as follows:

  • ./xc/evirtual unit unit1 as - after the action is called, the virtual unit changes its status normally
  • ./xc/evirtual unit unit1 is - does not change its status and does not report an error
  • ./xc/evirtual unit unit1 av - changes its value normally
  • ./xc/evirtual unit unit1 iv - does not change its value and does not report an error
  • ./xc/evirtual unit unit1 a - changes both status and value
  • ./xc/evirtual unit unit1 i - does not change neither status nor value without reporting an error

Simulation of action delay for the unit is set up as follows:

./xc/evirtual unit unit1 d 2.5

after the action is received, the unit simulates delay, e. g. 2.5 sec (in this example)

Simulation of the action runtime failure for the unit is set up as follows:

./xc/evirtual unit unit1 e 1

after the action is received, the program exits with the error code 1. To disable the error code, set it to 0

For all items: to simulate, let’s say, a 3.5-second delay when the UC starts a passive status update

./xc/evirtual unit unit1 ud 3.5 ./xc/evirtual sensor temp1 ud 3.5 ./xc/evirtual mu multiupdate1 ud 3.5

For all items: to complete the passive state update with the error code 1

./xc/evirtual sensor temp1 ue 1

To disable the error code, set it to 0.

Logic control macros

In Logic Manager macros can be triggered on the list of events, third-party applications or user via LM EI interface or LM API functions.

Macro code is a file written in Python and located in the folder xc/lm/ under the name <macro_id>.py, i.e. test.py for “test” macro. Macro id should be unique within the single LM PLC, full id (group/id) - within the whole installation.

Additionally, each macro is automatically appended with common.py file located in the same folder enabling to quickly assign common functions to several macros without using modules.

Macros are compiled into byte-code each time after macros file or common.py file are changed. Compilation or execution errors can be viewed in the log files of the controller.

Contents

Executing macros

To execute a macro, use macro run command of lm-cmd or LM API run function.

Debugging macros

Macro compilation and execution errors are written into the logs of the controller on DEBUG level, the exceptions are also added to err field of the execution result.

To receive information about errors you may run the following command:

lm-cmd -J run <macro_id> -w 3600 | jq -r .err

Macros configuration

After the macro code is placed into xc/lm/<macro_id>.py file, it should be appended to the controller using create_macro LM API function or with lm-cmd.

After the macro configuration is created, you may view its params using list_macro_props and change them with set_macro_prop.

Parameters:

  • id macros id, can’t be modified after the macro is created
  • action_enabled true means macro can be executed (true by default)
  • action_exec controller gets the code of the macro from the file <macro_id>.py by default, use this parameter to assign another file
  • description macro description
  • group macro group (in difference to other objects, macro group can be changed after creation)
  • pass_errors if true, in case the function called by macro is completed with an exception, the controller ignores this and continues the code execution (false by default)

Common principles of macros operation

Macros are launched simultaneously: system does not wait for the completion of the macro and launches its next copy or another macro in parallel. If you want only one copy of macro to operate at the certain point of time or to block execution of other macros, use macro lock and unlock functions.

The system architecture does not provide the possibility to stop macro from outside, that is why macros should have minimum internal logic and cycles.

All the logic should be implemented in the decision-making matrix. The working cycles should be implemented with logic variables timers.

System macros

If defined, macro named system/autoexec is launched automatically at the controller startup. This macro is not always the first one executed, as far as some initial decision-making rules may call assigned macros, or some events may be handled before. In case a macro is launched later than logic variables or other loadable items update their status (e. g. due to slow connection with MQTT server) it’s recommended to use sleep function to do a small delay.

Macros from system group are considered as the local system macros and aren’t synchronized to SFA.

Example of autoexec macro usage:

# both cycle timers are expired
if is_expired('timers/timer1') and is_expired('timers/timer2'):
    # launch the first cycle process
    action('pumps/pump1', on)
    # start the first cycle timer
    reset('timers/timer1')

Macros and security

As all Python features are available for macros, including execution of external programs or working with any local files, the code of macros should be edited only by system administrator.

If access permissions to individual macros are configured via API keys, you should take into account the following: if a macro runs other macros using run function, these macros will be executed even if the API key allows to run only the initial macro.

Macros built-ins

Macros can execute any Python functions or use Python modules installed on the local server. In addition, macros have a set of built-in functions and variables.

Built-in functions are included for quick access to the most frequently used Python functions such as LM API and UC API. When calling API function, item id is always transmitted in full. When calling other macros and working with logic variables, it’s possible to use the short ids only.

Variables

Macros have the following built-in variables:

  • on alias to integer 1
  • off alias to integer 0
  • yes alias to boolean True
  • no alias to boolean False
  • _source item generated the event, used by the system to call the macro. You may directly access the item and e.g. use its internal variables such as _source.item_id, _source.full_id, _source.oid etc.
  • argv array list of arguments the macro is being executed with
  • _0 current macro id (i.e. ‘test’)
  • _00 current macro full id (i.e. ‘group1/test’)
  • _1, _2, … _9 first 9 arguments the macro is being executed with
  • lm_cvars all lm_cvars variables
  • out macro may use this variable to output the data which will be set to out field of the execution result

Note

if macro arguments or lm_cvars are numbers, they are automatically converted to float type

Log messaging functions

Macros may send messages to the log of the controller with the following functions:

  • debug(msg) send DEBUG level message
  • info(msg) send INFO level message
  • warning(msg) send WARNING message
  • error(msg) send ERROR message
  • critical(msg) send CRITICAL message

In addition, print function is an alias of info.

Shared variables

Apart from the logic variables macros, can exchange variables between each other within the single controller with the following functions:

  • shared(varname, default_value) get value of the shared variable or return default_value if shared variable doesn’t exist

set_shared(varname, value) set value of the shared variable

Shared variables are not saved in case the controller is restarted.

Locking features

These functions implement internal locking which may be used e.g. to block other macros to run until the current one is finished.

lock - lock token request
lock(lock_id, timeout=None, expires=None)

params:

  • lock_id unique lock id (defined by user)
  • timeout lock request timeout (in seconds)
  • expires time (seconds) after which the lock is automatically released

Returns True, if lock has been requested successfully, False in case of failure.

Raises an exception if the parameter pass_errors=false is set in the macro config and the locking wasn’t successful.

unlock - release lock token
unlock(lock_id)

params:

  • lock_id unique lock id (defined by user)

Returns True if the lock is unlocked, False, if the lock does not exist.

Item functions

The following functions are used to control the items:

lvar_status - get logic variable status
lvar_status(lvar_id)

params:

Returns status (integer) of logic variable, None if the variable is not found.

Raises an exception if the parameter pass_errors=false is set in the macro config and the variable is not found.

lvar_value, value - get logic variable value
lvar_value(lvar_id, default='')
# is equal to
value(lvar_id, default='')

params:

Returns value (float if the value is numeric) of logic variable, None if variable is not found. If the value is null, returns an empty string by default or default value if specified.

Raises an exception if the parameter pass_errors=false is set in the macro config and the variable is not found.

Note

You may use value function to return sensor or unit values specifying their OIDs

set - set logic variable value
set(lvar_id, value=None)

params:

  • lvar_id logic variable id (full or short)
  • value value to set. If not specified, variable is set to null

Returns True on success, False if the variable is not found.

Raises an exception if the parameter pass_errors=false is set in the macro config and the variable is not found.

clear - stop timer/flag clear

If lvar is being used as a timer and has expires set, this function sets its status to 0 which works like a timer stop.

If lvar is used as a flag and has no expiration, this sets its value to 0 which works like setting flag to False

clear(lvar_id)

params:

Returns True on success, False if the variable is not found.

Raises an exception if the parameter pass_errors=false is set in the macro config and the variable is not found.

toggle - toggle a flag value

Sets lvar value to 1 if it has value “0”, otherwise “1”. If lvar is used as a flag, this works like a switching between False and True.

toggle(lvar_id)

params:

Returns True on success, False if the variable is not found.

Raises an exception if the parameter pass_errors=false is set in the macro config and the variable is not found.

# You may use this function to toggle unit status  specifying its OID:
toggle(unit_oid, wait=0, uuid=None, priority=None)
expires - set the lvar expiration time

Function is used to set/change lvar expiration time and is useful for changing timers’ durations.

expires(lvar_id, etime=0)

params:

  • lvar_id logic variable id (full or short)
  • etime new expiration time (in seconds)

If expiry is not defined or set to zero, the function stops the timer, but apart from clear completely disables the timer by setting its expiration to 0. To return the timer back to work, set its expiration time back after the timer reset (not before!).

Returns True on success, False if the variable is not found.

Raises an exception if the parameter pass_errors=false is set in the macro config and the variable is not found.

is_expired - check timer expiration

Function is useful when lvar is used as a timer to quickly check if it’s still running or not.

is_expired(lvar_id)

params:

Returns True if lvar has expired status (timer is finished), equal to checking status==1 and value==’‘, False if lvar is not expired or not found.

Raises an exception if the parameter pass_errors=false is set in the macro config and the variable is not found.

status - get item status
status(oid)

params:

  • oid item oid (type:group/id)

Returns status (integer) of the item, None if the item is not found.

Raises an exception if the parameter pass_errors=false is set in the macro config and the item is not found.

value - get item value
value(oid)

params:

  • oid item oid (type:group/id)

Returns value (float if the value is numeric) of the item state, None if the item is not found. If the value is null, returns an empty string.

Raises an exception if the parameter pass_errors=false is set in the macro config and the item is not found.

unit_status - get unit status
unit_status(unit_id)

params:

  • unit_id unit id (full)

Returns status (integer) of the unit, None if the unit is not found.

Raises an exception if the parameter pass_errors=false is set in the macro config and the unit is not found.

unit_value - get unit value
unit_value(unit_id, default='')
# is equal to
value(unit_oid, default='')

params:

  • unit_id unit id (full)

Returns value (float if the value is numeric) of the unit state, None if the unit is not found. If the value is null, returns an empty string by default or default value if specified.

Raises an exception if the parameter pass_errors=false is set in the macro config and the unit is not found.

unit_nstatus - get unit future status
unit_nstatus(unit_id)
# or
nstatus(unit_id)

params:

  • unit_id unit id (full)

Returns future status (integer) of the unit, None if the unit is not found. If the unit has no action running, future status is equal to the current.

Raises an exception if the parameter pass_errors=false is set in the macro config and the unit is not found.

unit_nvalue - get unit future value
unit_nvalue(unit_id)
# or
nvalue(unit_id)

params:

  • unit_id unit id (full)

Returns value (float if the value is numeric) of the unit state, None if the unit is not found. If the value is null, returns an empty string. . If the unit has no action running, future state value is equal to the current.

Raises an exception if the parameter pass_errors=false is set in the macro config and the unit is not found.

is_busy - check if the unit has action running

Compares current and future unit state, the difference means the unit is currently is running an action and is busy.

is_busy(unit_id)

params:

  • unit_id unit id (full)

Returns True if the unit is currently running an action and its future state is different from the current. False if the states are equal and it means the unit has no action running, None if the unit is not found.

Raises an exception if the parameter pass_errors=false is set in the macro config and the unit is not found.

history - get lvar state history

Returns list or dict with state history records for the specified lvar(s).

history(lvar_id, t_start=None, t_end=None, limit=None, prop=None,
    time_format=None, fill=None, fmt=None, db=None):

params:

  • lvar_id lvar ID, or multiple IDs, comma separated
  • t_start time frame start, ISO or Unix timestamp
  • t_end time frame end, optional (default: current time), ISO or Unix timestamp
  • limit limit history records (optional)
  • prop item property (status or value)
  • time_format time format (iso or raw for Unix timestamp)
  • fill fill frame with the specified interval (e.g. 1T - 1 minute, 2H - 2 hours etc.), optional
  • fmt output format, ‘list’ (default) or ‘dict’
  • db notifier ID which keeps history for the specified item(s) (default: db_1)

To get state history for the multiple items:

  • fill param is required
  • fmt should be specified as list

Raises an exception if the parameter pass_errors=false is set in the macro config and the lvar or history database is not found.

action, action_toggle, start, stop - start unit action

Starts the action for the unit.

action(unit_id, status, value=None, wait=0, uuid=None, priority=None)
# same as action with status=1
start(unit_id, value=None, wait=0, uuid=None, priority=None)
# same as action with status=0
stop(unit_id, value=None, wait=0, uuid=None, priority=None)
# same as start (without a value) if status=0 or stop if status=1
action_toggle(unit_id, wait=0, uuid=None, priority=None)
# if unit OID (unit:group/unit_id) is specified, you may use "toggle"
# instead:
toggle(unit_oid, wait=0, uuid=None, priority=None)

params:

  • unit_id unit id (full)
  • status unit new status
  • value unit new value
  • wait wait (seconds) for the action execution
  • uuid set action uuid (generated automatically if not set)
  • priority action priority on the controller (default 100, lower value means higher priority)

Returns result in the same dict format as UC API action function, None if the unit is not found.

Raises an exception if the parameter pass_errors=false is set in the macro config and the unit is not found.

result - get action result

Obtain action result, either all results for the unit by unit_id or the particular action result by uuid

result(unit_id=None, uuid=None)

params:

  • unit_id unit id (full)
  • uuid action uuid

Either unit_id or uuid must be specified. The controller can obtain the result by uuid only if the action was executed by its API or macro function and the controller hasn’t been restarted after that.

Returns result in the same dict format as UC API result function, None if the unit is not found or controller doesn’t know about the action with the specified uuid.

Raises an exception if the parameter pass_errors=false is set in the macro config and the unit is not found.

Note

macro result function returns the execution result of the unit action, while result function of LM API returns the execution results of local macros only.

terminate - terminate the current action

Terminate the current unit action, either by unit_id or by action uuid

terminate(unit_id=None, uuid=None)

params:

  • unit_id unit id (full)
  • uuid action uuid

Either unit_id or uuid must be specified. The controller can terminate the action by uuid only if it was executed by its API or macro function and the controller hasn’t been restarted after that.

Returns termination result in the same dict format as UC API terminate function, None if the unit is not found, the controller doesn’t know about the action with the specified uuid or the remote action doesn’t exist (or is already finished).

Does not raise any exceptions.

q_clean - clean unit action queue

Cleans the unit action queue but keeps the current action running if it already has already been started.

q_clean(unit_id=None)

params:

  • unit_id unit id (full)

Returns queue clean result in the same dict format as UC API q_clean function, None if the unit is not found.

Does not raise any exceptions.

kill - clean unit queue and terminate current action

Cleans the unit action queue and terminates the current action running if it has already been started.

kill(unit_id=None)

params:

  • unit_id unit id (full)

Returns queue clean result in the same dict format as UC API kill function, None if the unit is not found.

Does not raise any exceptions.

sensor_status - get sensor status
sensor_status(sensor_id)

params:

Returns status (integer) of sensor, None if the sensor is not found.

Raises an exception if the parameter pass_errors=false is set in the macro config and the sensor is not found.

sensor_value - get sensor value
sensor_value(sensor_id, default='')
# is equal to
value(sensor_oid, default='')

params:

Returns value (float if the value is numeric) of sensor state, None if the sensor is not found. If the value is null, returns an empty string by default or default value if specified.

Raises an exception if the parameter pass_errors=false is set in the macro config and the sensor is not found.

Rule management functions

set_rule_prop - set rule parameters
set_rule_prop(rule_id, prop, value=None, save=False)

Allows to set configuration parameters of the rule.

Parameters:

  • rule_id rule id
  • prop rule configuration param
  • value param value

optionally:

  • save If True, save unit configuration on disk immediately after creation

Device management functions

Macros can create, update and destroy devices with pre-defined device templates.

create_device - create device items
create_device(controller_id, device_tpl, cfg=None, save=None):

params:

  • controller_id connected Universal Controller ID
  • device_tpl device template, stored on the connected controller in runtime/tpl
  • cfg configuration params
  • save If True, save items configuration on disk immediately after operation

Raises an exception if the parameter pass_errors=false is set in the macro config and the access error has occured.

update_device - update device items

Works similarly to create_device - create device items function but doesn’t create new items, updating item configuration of the existing ones.

update_device(controller_id, device_tpl, cfg=None, save=None):

Parameters and return data are the same.

destroy_device - destroy device items

Works in opposite way to create_device - create device items function, destroying all items specified in the template.

destroy_device(controller_id, device_tpl, cfg=None)

Parameters and return data are the same except that the function doesn’t have save param.

File management functions

ls - list files by mask
ls(mask)

params:

  • mask file mask to list (i.e. /var/folder1/*.jpg)

Returns file listing by the specified mask as an array:

[{
     "name": "1.png",
     "size": 2443,
     "time": {
         "c": 1507735364.2441583,
         "m": 1507734605.1451921
     }
 },
 {
     "name": "2.png",
     "size": 2231,
     "time": {
         "c": 1507735366.5561802,
         "m": 1507735342.923956
     }
 }]

where

  • size file size (in bytes)
  • time/c inode creation time (ctime, UNIX timestamp)
  • time/m file modification time (mtime)
open_newest - open newest file

Tries to find and open the newest file by the specified mask. Useful i.e. for the folders where security cameras periodically upload an images.

open_newest(mask, mode='r', alt=True)

params:

  • mask file mask to search in (i.e. /var/folder1/*.jpg)
  • mode file open mode
  • alt open alternative (the second newest) file if there’s error opening the newest one (i.e. when the newest file it’s still uploading)

Returns a file stream.

Raises an exception if the parameter pass_errors=false is set in the macro config and the file can not be opened.

open_oldest - open oldest file

Tries to find and open the oldest file by the specified mask.

open_oldest(mask, mode='r')

params:

  • mask file mask to search in (i.e. /var/folder1/*.jpg)
  • mode file open mode

Returns a file stream.

Raises an exception if the parameter pass_errors=false is set in the macro config and the file can not be opened.

System functions

alias - create object alias

The functions allows to create object alias, e.g. make a short alias for a long-named function.

alias(alias_obj, src_obj)

params:

  • alias_obj alias object
  • src_obj source object

Returns True if alias is set, False if not (e.g. src_obj is not found).

Usage example: you have a function very_long_function and want to make f1 alias for it. All you need is to put in xc/lm/common.py the following code:

alias('f1', 'very_long_function')

The difference between Python code f1=very_long_function is that such code will throw an exception if very_long_function is not found, while alias macro function will pass an error and return False.

sleep - pause operations
# alias for python time.sleep
sleep(seconds.milliseconds)
time - get current UNIX timestamp
# alias for python time.time
time()
mail - send email message
mail(subject=None, text=None, rcp=None)

params:

  • subject email subject
  • text email text
  • rcp recipient or array of the recipients

The function uses [mailer] section of the LM PLC configuration to get sender address and list of the recipients (if not specified).

Returns True if the message is sent successfully.

get - HTTP/GET request
# alias for requests.get
get(uri, args)

See requests documentation for more info.

post - HTTP/POST request
# alias for requests.post
post(uri, args)

See requests documentation for more info.

system - execute OS command
# alias for python os.system
system(command)
cmd - execute command script

Executes a command script on the chosen controller.

cmd(controller_id, command, args=None, wait=None, timeout=None)

params:

  • controller_id controller id where the script is located (full or short)
  • command script command name
  • args script command arguments (array or separated with spaces in a string)
  • wait wait for the command result (in seconds)
  • timeout max command execution time

Returns the result equal to the result of SYS API cmd function.

run - execute another local macro
run(macro_id, argv=None, wait=0, uuid=None, priority=None)

params:

  • macro_id local macro id (full or short)
  • argv execution arguments
  • wait wait (in seconds) for the result
  • uuid macro action uuid (generated automatically if not set)
  • priority action priority (default 100, lower value means higher priority)

Returns the result equal to the result of LM API run function.

exit - exit macro

Finishes macro execution

exit(code=0)

params:

  • code macro exit code (0 - no errors)

Extending macros functionality

Macros function set can be extended with pre-made or custom macro extensions. As soon as extension is loaded, its functions become available in all macros without a need to restart LM PLC.

Decision-making matrix

Decision-making matrix is a set of rules and conditions under which Logic Manager runs the specified macros when certain events occur.

To change the decision rules you may use lm-rules, lm-cmd console applications, LM API functions or an appropriate LM EI interface section.

Rule configuration is stored in runtime/lm_dmatrix_rule.d/ folder.

Event analysis algorithms

Event means any change of the item state. The events are analyzed and processed in the following way:

  • A specific ID is assigned to each event by which you can monitor its processing in the logs.

  • The system takes the list of all rules sorted by their priority (the lower the value, the higher the priority) and description.

  • Each rule is analyzed separately, in the order of priority

  • Firstly, it’s checked whether the rule corresponds to the type, group, ID and property of the item that sent the event

  • Then, the system is verifying whether the current item state matches the rule conditions and the previous one is out of its range

    • For example, there is a temperature sensor; the condition 25 <= x <= 28 is specified in the rule; it will match only once - as soon as the transmitted temperature reaches 25-28 degrees. The rule will match again if the temperature exceeds this range and returns back.
    • When the controller is just started, the previous state is unknown or usually outdated. In this case, the system acts according to the configuration property of the rule for_initial: if it’s set to skip, the rule is ignored, if only or any, it’s verified and the rule matches if the current state matches the range.
    • If for_initial is set to only in the rule configuration, the rule is being checked only once, after the controller is started and the initial states of the items are received.
    • Logic variables always have an initial value stored in the local base, that’s why for_initial should always be any for them to let the rules work correctly, unless you really know what you do.
    • If the rule matches, a macro (if specified) with the specified arguments is executed.
    • If chillout_time > 0 in the configuration, the rule is ignored after the match for the specified time.

Rule configuration

Unmodifiable rule parameters:

  • id rule id, always generated automatically when it is created
  • chillout_ends_in a virtual parameter specifying for how long (in seconds) the rule is ignored, if chillout_time is set
  • condition a virtual parameter displaying a rule condition in the readable format (i.e. 25 < x <= 28)

Modifiable Parameters:

  • break_after_exec if True and the rule matches, further rules for the event are ignored
  • chillout_time the rule is ignored for a specified time (in seconds) after match
  • description rule description
  • enabled if True, rule is enabled (new rules are disabled by default)
  • for_initial can be skip, only or any (default is any). Indicates the rule processing algorithm when the server is started and the initial item states are received
  • for_item_group the rule matches only for a specific group of items ((# or null - for all groups)
  • for_item_id the rule matches only for a specific item (# or null - for all items), may contain the mask *id, id* or *id*, i.e. *.temperature
  • for_prop the state property of the item (status or value) the rule is checking. For unit state, nstatus and nvalue properties may be additionally used.
  • in_range_max matches when x < value
  • in_range_max_eq matches when x <= value (in_range_max should be specified)
  • in_range_min matches when x > value
  • in_range_min_eq matches when x >= value (in_range_min should be specified)
  • macro macro that is executed when the rule conditions match
  • macro_args arguments the macro is executed with
  • priority the rule priority (integer; the lower the value, the higher the priority, 100 by default)

Tips for rule configuration

  • to set “x == value” condition via lm_api: if the value is numeric, use “value <= x <= value”. If the value is string, you may set only in_range_min_eq
  • if you set a field for_expire (with any value, i.e. Y) in a rule change request, the system automatically sets the rule to for_prop = status, x <= -1, which means the rule match when the item state is expired. This is useful to configure the rule to check for the lvar timers expiration as well as checking for units and sensors error states
  • if you set a field for_set (with any value, i.e. Y) in a rule change request, the system automatically sets the rule to for_prop = status, x == 1, which means the rule match when the item state is set. This is useful to configure the rule to check for the lvar timers reset as well as working with a logical flags
  • to delete in_range_min and in_range_max conditions, use null or none in lm-rules or blank value in LM API set_rule_prop
  • if the rule has no in_range_min and in_range_max conditions, it will match each time when the item changes its status (for_prop == status) or value (for_prop == value)

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);
        }
   }

SFA Templates

SCADA Final Aggregator uses jinja2 template engine to serve server-side templates. You can use SFA templates for regular HTML, javascript and JSON data files. Both ui and pvt folders can contain template files, the difference is only that templates in ui are public while templates in pvt are served via SFA PVT.

Template files

All files with .j2 extension are processed as templates, index.j2 has more priority than index.html as the primary interface page.

Templates support all jinja2 functions and features, plus have some specific built-in variables and functions.

Template variables

The following variables are available in all templates:

  • All custom user-defined variables
  • server contains a dict with a system and current API key info (equal to SYS API test function result) plus an additional key remote_ip which contains either request IP address or value of X-Real-IP variable (if set by frontend server).
  • request contains a dict with all request GET or POST params.

Template functions

All templates have the following built-in functions:

groups

Get list of item groups

groups(g=None, p=None, k=None)

where:

The function is similar to SFA API groups except that if API key is not specified, the current key is used.

state

Get list of items and their state

state(i=None, g=None, p=None, k=None):

where:

  • i full item id (group/id), optional
  • g filter by group (use MQTT-style wildcards)
  • p item type (U for unit, S for sensor, LV for lvar), required
  • k API key (use key ID instead of key itself)

The function is similar to SFA API state except that if API key is not specified, the current key is used.

SFA PVT

While developing the interfaces for SCADA Final Aggregator you face the issue of the private data protection: the UI is loaded with the javascript application that runs in the browser and requires authentication to access the SFA API functions. However, the application may contain components which an unauthorized user should not see: plans of the building, security cam footages, even the list of the managed items may be confidential.

One way to solve this problem is to use the frontend server for such content. However, frontend is not always necessary and, in our case, the content structure often requires the access rights to certain parts to be set on the front-end. Therefore, it may involve duplicating user base and difficult integration of the additional authentication methods.

In most cases, it would be sufficient to delineate access to such content with the help of SFA PVT server. The access rights to the certain files and catalogs are regulated with pvt parameter in SFA API keys.

The PVT server interface is available at http(s)://<IP_address_SFA:Port>/pvt, and the private content should be placed in pvt folder of EVA root directory.

pvt parameter of API keys supports MQTT-style wildcards, i.e.:

pvt = map.jpg, c1/#, +/content.js

will give the key access to map.jpg, all files and subfolders of c1 folder as well as content.js file in any first-level folder.

If the client is authenticated in advance, the future requests do not require k=APIKEY param.

Loading files from PVT Server

The file can be loaded with the following request:

http(s)://<IP_address_SFA:Port>/pvt?k=APIKEY&f=FILE

where

  • k valid API key
  • f a full relative file path, i.e. map.jpg or c2/content.js

Receiving the file list

Use c=list request param to receive the file list by the specified mask:

http(s)://<IP_address_SFA:Port>/pvt?k=APIKEY&f=FILEMASK&c=list

The mask should be included in the pvt key access right parameter, for example

pvt = c1/*.json ; or c1/# for all files and masks

The complete request example:

http(s)://<IP_address_SFA:Port>/pvt?k=APIKEY&f=c1/*.png&c=list

which return JSON array:

[{
     "name": "1.png",
     "size": 2443,
     "time": {
         "c": 1507735364.2441583,
         "m": 1507734605.1451921
     }
 },
 {
     "name": "2.png",
     "size": 2231,
     "time": {
         "c": 1507735366.5561802,
         "m": 1507735342.923956
     }
 }]

where

  • size file size (in bytes)
  • time/c inode creation time (ctime, UNIX timestamp)
  • time/m file modification time (mtime)

Receiving the newest and the oldest file

Use c=newest (c=oldest) param to do the typical job of the management interfaces - receiving the newest file from the specified folder.

http(s)://<IP_address_SFA:Port>/pvt?k=APIKEY&f=FILEMASK&c=newest

Example: there is a monitoring camera that uploads a file to the folder on the server every 10 seconds. The uploaded files are named, i.e. TIMESTAMP.jpg or ID.jpg.

Connect the file with these images to pvt:

cd pvt
ln -sf /path/to/camerafolder cam1

and easily receive the newest file with the following request:

http(s)://<IP_address_SFA:Port>/pvt?k=APIKEY&f=cam1/*.jpg&c=newest

Image Processing

Use ic=resize to ask the server to preprocess the image file. To let the server process images, Python PIL (pillow) library should be installed. EVA installer automatically installs the library using pip3.

Please, make sure that system has at least libjpeg-dev and libjpeg8-dev before EVA setup, otherwise, PIL won’t work with JPEG images.

In case you miss this and server returns an error (“decoder not available”), reinstall pillow:

pip3 install --no-cache-dir -I pillow

If everything is installed correctly, you can receive the processed image using the following request:

http(s)://<IP_address_SFA:Port>/pvt?k=APIKEY&f=FILE&ic=resize:XxYxQ:encoder

where:

  • X and Y - image maximum width/height
  • Q image quality
  • encode image encoder

I.e. let’s get an image pvt/cam/1.jpg, resize it to 800x600 as max, and convert to JPEG with 90% quality:

http(s)://<IP_address_SFA:Port>/pvt?k=APIKEY&f=cam1/1.jpg&ic=resize:800x600x90:jpeg

We may combine ic with c param, allowing us to receive the newest file by the mask. The request

http(s)://<IP_address_SFA:Port>/pvt?k=APIKEY&f=cam1/*.jpg&c=newest&ic=resize:800x600x50:jpeg

will return the newest jpeg file from cam1 folder having scaled the image size to max 800x600 (proportionally) and reduced its quality to 50%. If the newest file cannot be processed (for example, the image isn’t completely loaded by cam yet and the file is locked), the server will attempt to process the previous one.

If the content is processed immediately before its loading by the interface, the server won’t need to generate the unnecessary images, especially if every client demands a specific format.

The maximum size of source file for the image processing is 10 megabytes.

Disabling cache

To ensure the request cashing is disabled, add nocache parameter with any value:

http(s)://<IP_address_SFA:Port>/pvt?k=APIKEY&f=FILE&nocache=VALUE

if you use this parameter for requests, web browser will not cache a file (if random value is used). Besides, the server will set Cache-Control, Expires and Pragma headers to the values which prohibit any caching.

Using frontend

If you work via frontend, you can use the pvt folder as a usual one (in case the authentication succeeded) by accessing files by their path without f parameter. Example for NGINX:

location / {
    rewrite ^/pvt/(.+)$ /pvt?f=$1 last;
}

The additional commands will keep working:

GET "http://eva.sfa.domain/pvt/data/cam3/*.jp*?c=newest&ic=resize:320x200x50:jpeg&nocache=1508070344872"

Remote content

SFA PVT can act as a proxy, fetching allowed resources in local network and displaying them to user.

This can be done with request

http(s)://<IP_address_SFA:Port>/rpvt?k=APIKEY&f=http://remote_host/folder/file&nocache=some_random_value

Param nocache is optional. If user is logged in, param k can be omitted.

Example: you have a chart on storage server in local network displaying storage usage. The chart is located at http://192.168.1.20/charts/zfs.png

Append the following permission string to API key:

rpvt = 192.168.1.20/charts/#

This will grant access to all files on the specified host in /charts/ folder.

Then include remote chart in your interface:

<img src="/rpvt?k=APIKEY&f=192.168.1.20/charts/zfs.png" />

As you see, the remote client doesn’t need to have a direct access to 192.168.1.20 web server, /rpvt API call acts for him as a content proxy.

To use remote content feature, you must follow the rules:

  • protocol (http/https) doesn’t need to be specified in rpvt API key param.
  • f param of /rpvt request may contain uri protocol (e.g. http://192.168.1.20/charts/zfs.png). If the protocol is not specified, SFA uses plain HTTP without SSL.
  • You can not specify http(s) port in f param of /rpvt unless it’s also specified in rpvt API key param.
  • Avoid using rpvt = #, this will allow /rpvt to work as http proxy for any local and Internet resource and may open a security hole.

SYS API

SYS API is a common API present in all EVA controllers. SYS API functions are used to manage controller itself.

SYS API is called through URL request

http://<IP_address:Port>/sys-api/function

If SSL is allowed in the controller configuration file, you can also use https calls.

All functions can be called using GET and POST methods. When POST method is used, the parameters can be passed to functions either as www-form or as JSON.

test - test API/key and get system info

Test can be executed with any valid API key of the controller the function is called to.

Parameters:

  • k valid API key

Returns JSON dict with system info and current API key permissions (for masterkey only ‘master’:true is returned)

{
    "acl": {
        "allow": {
            "cmd": true
        },
        "groups": [
            "room1/#",
            "windows",
            "hall/+"
        ],
       "items": [],
        "key_id": "key1",
        "master": false,
        "sysfunc": false
    },
    "product_build": 2017082101,
    "product_code": "uc",
    "product_name": "EVA Universal Controller",
    "result": "OK",
    "system": "eva3-test1",
    "time": 1504489043.4566338,
    "version": "3.0.0"
}

Errors:

  • 403 Forbidden the key has no access to the API.

cmd - execute a remote command

Executes a command script on the server where the controller is installed.

Parameters:

  • k API key with “allow=cmd” permission
  • c name of the command script
  • a command arguments (passed to the script)
  • w wait (in seconds) before API call sends a response. This allows to try
    waiting until command finish
  • t maximum time of command execution. If the command fails to finish
    within the specified time (in sec), it will be terminated

Returns JSON dict

{
   "args": [ "<specified>", "<command>", "<parameters>" ],
   "cmd": "<command>",
   "err": "<stderr output>",
   "exitcode": <script exit code>,
   "out": "<stdout output>",
   "status": "<current_status>",
   "time": {
       "<status1>": <UNIX_TIMESTAMP>,
       "<status2>": <UNIX_TIMESTAMP>
   },
   "timeout": "<specified_max_execution_time>"
}

If API failed to wait for the command execution results (t < w), the status will be returned as “running”. In case the command is complete, the status will be one of the following:

  • completed command succeeded
  • failed command failed (exitcode > 0)
  • terminated command is terminated by timeout/by system or the requested
    script was not found

Errors:

  • 403 Forbidden the API key has no access to this function

lock - lock token request

Lock tokens can be used similarly to file locking by the specific process. The difference is that SYS API tokens can be:

  • centralized for several systems (any EVA server can act as lock server)
  • removed from outside
  • automatically unlocked after the expiration time, if the initiator failed or forgot to release the lock

used to restrict parallel process starting or access to system files/resources.

Important: even if different EVA controllers are working on the same server, their lock tokens are stored in different bases. To work with the token of each subsystem, use SYS API on the respective address/port.

Parameters:

  • k API key with “allow=lock” permissions
  • l lock ID (arbitrary)
  • t maximum timeout (seconds) to get token (optionally)
  • e time after which token is automatically unlocked (if absent, token may
    be unlocked only via unlock function)

returns JSON dict { “result”: “OK” }, if lock has been received or { “result”: “ERROR” }, if lock failed to be obtained

Errors:

  • 403 Forbidden the API key has no access to this function

unlock - release lock token

Releases the previously requested lock token.

Parameters:

  • k API key with “allow=lock” permissions
  • l lock token ID

returns JSON dict { “result” : “OK” }. In case token is already unlocked, remark = “notlocked” note will be present in the result.

Errors:

  • 403 Forbidden the API key has no access to this function
  • 404 Not Found token not found

log_rotate - rotate controller’s log file

Rotates log file similarly to kill -HUP <controller_id>

Parameters:

  • k API key with “sysfunc=yes” permissions

returns JSON dict { “result” : “OK” }

Errors:

  • 403 Forbidden the API key has no access to this function

log_debug, log_info, log_warning, log_error, log_critical - write to the log

An external application can put a message in the logs on behalf of the controller.

Parameters:

  • k API key with “sysfunc=yes” permissions
  • m message to log

returns JSON dict { “result” : “OK” }

Errors:

  • 403 Forbidden the API key has no access to this function

log_get - get log records

This command allows to read log records from the controller. Log records are stored in the controllers’ memory until restart or the time (keep_logmem) specified in controller configuration passes.

Note

this doesn’t allow you to obtain records stored in log files, only the records currently kept in memory

Parameters:

  • k API key with “sysfunc=yes” permissions

Optionally:

  • l log level (10 - debug, 20 - info, 30 - warning, 40 - error, 50 -
    critical)
  • t get log records not older than t seconds
  • n the maximum number of log records you want to obtain

returns JSON dict { “result” : “OK” }

Errors:

  • 403 Forbidden the API key has no access to this function

set_debug - switch debugging mode

Enables and disables debugging mode while the controller is running. After the controller is restarted, this parameter is lost and controller switches back to the mode specified in the configuration file.

Parameters:

  • k API key with “sysfunc=yes” permissions
  • debug 1 for enabling debug mode, 0 for disabling

returns JSON dict { “result” : “OK” }

Errors:

  • 403 Forbidden the API key has no access to this function

get_cvar - get variable

Returns one or all user-defined variables.

Important: even if different EVA controllers are working on the same server, they have different sets of variables To set the variables for each subsystem, use SYS API on the respective address/port.

Parameters:

  • k API key with masterkey permissions
  • i variable name (if not specified, all variables will be returned)

Returns JSON dict

{
    "VARIABLE" : "VALUE"
}

Errors:

  • 403 Forbidden the API key has no access to this function
  • 404 Not Found the specified variable is not defined

set_cvar - set variable value

Sets the value of user-defined variable.

Parameters:

  • k API key with masterkey permissions
  • i variable name
  • v variable value (if omitted, variable is deleted)

returns JSON dict { “result” : “OK” }

Errors:

  • 403 Forbidden the API key has no access to this function

save - save database and runtime configuration

All modified items, their status, and configuration will be written to the disk. If exec_before_save command is defined in the controller’s configuration file, it’s called before saving and exec_after_save after (e.g. to switch the partition to write mode and back to read-only).

Parameters:

  • k API key with “sysfunc=yes” permissions

returns JSON dict { “result”: “OK” }

Errors:

  • 403 Forbidden the API key has no access to this function

Notifier management

These functions allow you to manage notifiers while EVA component is running. All changes are applied temporarily and are discarded after controller restart.

notifiers - get list of notifiers

Get the list of configured notifiers as well as their configuration.

Parameters:

  • k API key with masterkey permissions

returns JSON array of the notifiers available on the controller.

Errors:

  • 403 Forbidden the API key has no access to this function
enable_notifier

Enables selected notifier

Parameters:

  • k API key with masterkey permissions
  • i notifier ID

returns JSON dict { “result”: “OK” }

Errors:

  • 403 Forbidden the API key has no access to this function
disable_notifier

Disables selected notifier

Parameters:

  • k API key with masterkey permissions
  • i notifier ID

returns JSON dict { “result”: “OK” }

Errors:

  • 403 Forbidden the API key has no access to this function

API key management

Each EVA component allows you to manage its API keys. Keys, stored in configuration files are called static and can not be managed. Also you can not dynamically create keys with masterkey permissions.

Each EVA controller has its own API key list written in the local database of the certain server by default. If you set same userdb_file value in the controllers’ configurations, they will use a common key list.

list_keys - get API keys list

Get the list of available API keys

Parameters:

  • k API key with masterkey permissions

returns JSON array of the API keys available on the controller.

Errors:

  • 403 Forbidden the API key has no access to this function
create_key - create new API key

Creates new dynamic API key without any access permissions.

Parameters:

  • k API key with masterkey permissions
  • i new API key ID, required

Returns serialized key dict in case of succcess or JSON dict { “result”: “ERROR” } in case of error.

Errors:

  • 403 Forbidden the API key has no access to this function
list_key_props - get API key parameters

Allows to list API key parameters.

  • k masterkey
  • i API key ID

Errors:

  • 403 Forbidden invalid API KEY
set_key_prop - set API key parameters

Allows to set access parameters of API key.

Parameters:

  • k masterkey
  • i API key ID
  • p access param
  • v param value (if not specified - the param is cleared)

Returns result=”OK” if the parameter is set, or result=”ERROR”, if an error occurs. Paramters id and key can not be changed with this function.

Errors:

  • 403 Forbidden invalid API KEY
regenerate_key - regenerate existing API key

Allows to regenerate existing dynamic API key leaving its permissions unchanged.

Parameters:

  • k API key with masterkey permissions
  • i API key ID, required

Returns serialized key dict in case of succcess or JSON dict { “result”: “ERROR” } in case of error.

Errors:

  • 403 Forbidden the API key has no access to this function
destroy_key - delete API key

Deletes dynamic API key from the database.

Parameters:

  • k API key with masterkey permissions
  • i API key ID, required

returns JSON dict { “result”: “OK” }

Errors:

  • 403 Forbidden the API key has no access to this function

User management

Apart from authorization via API keys, requests to API can be authorized using login/password. A specific API key is assigned to each user (thhe same key can be assigned to multiple users) and its permissions are stored during login session.

The key assigned to user is used to authorize all the operations unless the other key is specified in the request.

Each EVA controller has its own user list written in the local database of the certain server by default. If you set same userdb_file value in the controllers’ configurations, they will use a common user list.

As far as controllers don’t write anything to the database during user authorization tasks, it can easily be stored on the network drive and used by EVA controllers running on different hosts.

list_users - get users list

Get the list of the defined users and API keys assigned to them

Parameters:

  • k API key with masterkey permissions

returns JSON array:

[
    {
        "key": "masterkey",
        "user": "admin"
    },
    {
        "key": "key1",
        "user": "eva"
    },
    {
       "key": "key1",
        "user": "john"
    },
    {
        "key": "op",
        "user": "operator"
    }
]

Errors:

  • 403 Forbidden the API key has no access to this function
create_user - create new user

Creates a new user in the database

Parameters:

  • k API key with masterkey permissions
  • u user login
  • p user password
  • a API key to assign

returns JSON dict { “result” : “OK”}

Errors:

  • 403 Forbidden the API key has no access to this function
set_user_password - change user password

Changes user password

Parameters:

  • k API key with masterkey permissions
  • u user login
  • p new password

returns JSON dict { “result” : “OK”}

Errors:

  • 403 Forbidden the API key has no access to this function
set_user_key - change assigned API key

Assigns another API key to user

Parameters:

  • k API key with masterkey permissions
  • u user login
  • a API key to assign

returns JSON dict { “result” : “OK”}

Errors:

  • 403 Forbidden the API key has no access to this function
destroy_user - delete user

Deletes user from the database

Parameters:

  • k API key with masterkey permissions
  • u user login

returns JSON dict { “result” : “OK”}

Errors:

  • 403 Forbidden the API key has no access to this function

File operations in runtime

SYS API allows operations with any text files in “runtime” folder. According to the program architecture, all files in this folder (except for databases) are text(JSON). To simplify working with files via API calls all requests and replies are made in text(JSON) format and no binary data is transferred.

For safety reasons these API functions must be enabled in advance with file_management=yes param in “sysapi” section of the controller’s configuration file.

file_get - get file from runtime

Gets a content of the file from runtime folder.

Parameters:

  • k API key with masterkey permissions
  • i path to file, relatively to runtime root, without / at the beginning

returns JSON dict:

{
    "data": "<FILE_CONTENT>",
    "file": "<FILE_NAME>",
    "result": "OK"
}

Errors:

  • 403 Forbidden the API key has no access to this function
  • 404 Not Found the file doesn’t exist
file_put - upload file into runtime

Puts a new file into runtime folder. If the file with such name exists, it will be overwritten.

Parameters:

  • k API key with masterkey permissions
  • i path to file, relatively to runtime root, without / at the beginning
  • m file content

returns JSON dict { “result” : “OK”}

Errors:

  • 403 Forbidden the API key has no access to this function
file_set_exec - file exec permission management

Sets file permissions to allow its execution.

Parameters:

  • k API key with masterkey permissions
  • i path to file, relatively to runtime root, without / at the beginning
  • e 0 to prohibit the file execution (permissions 0644), 1 - to allow
    (permissions 0755)

returns JSON dict { “result” : “OK”}

Errors:

  • 403 Forbidden the API key has no access to this function
  • 404 Not Found the file doesn’t exist

UC API

Universal Controller UC API is called through URL request

http://<IP_address_UC:Port>/uc-api/function

If SSL is allowed in the controller configuration file, you can also use https calls.

All functions can be called using GET and POST methods. When POST method is used, the parameters can be passed to functions either as www-form or as JSON.

Note

Object creation and modification functions don’t save configurations automatically unless you specify save parameter in API request. The system is designed to work this way to let you discard the changes in case of serious problems by killing the controller process.

If you need to save any changes made without this parameter, restart the controller gracefully or use SYS API save function.

Contents

test - test API/key and get system info

Test can be executed with any valid API KEY

Parameters:

  • k valid API key

Returns JSON dict with system info and current API key permissions (for masterkey only ‘master’:true is returned)

{
    "acl": {
        "allow": {
            "cmd": true
        },
        "groups": [
            "room1/#",
            "windows",
            "hall/+"
        ],
        "items": [],
        "key_id": "key1",
        "master": false,
        "sysfunc": false
    },
    "product_build": 2017082101,
    "product_code": "uc",
    "product_name": "EVA Universal Controller",
    "result": "OK",
    "system": "eva3-test1",
    "time": 1504489043.4566338,
    "version": "3.0.0"
}

Errors:

  • 403 Forbidden the key has no access to the API

state - get item state

State of the item or all items of the specified type can be obtained using state command.

Parameters:

  • k valid API key
  • i item ID
  • p item type (short forms U for unit, S for sensor may be used)
  • g group filter, optional mqtt masks can be used, for example group1/#, group1/+/lamps)
  • full if 1, display extended item info, optional (config_changed, description, virtual, status_labels and action_enabled for unit)

Returns item state in JSON dict or array of dicts:

[
    {
        "action_enabled": true,
        "full_id": "hall/lamps/lamp1",
        "group": "hall/lamps",
        "id": "lamp1",
        "nstatus": 1,
        "nvalue": "",
        "oid": "unit:hall/lamps/lamp1",
        "status": 1,
        "type": "unit",
        "value": ""
    }
]

where status and value - current item state, nstatus and nvalue (for unit) - expected status and value. Current and new status and value are different in case the action is executed for the unit at the moment. In all other cases, they are the same.

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found item doesn’t exist, or the key has no access to the item

state_history - get item state history

State history of one item or several items of the specified type can be obtained using state_history command.

Parameters:

  • k valid API key
  • i item ID, or multiple IDs, comma separated
  • a notifier ID which keeps history for the specified item(s) (default: db_1)
  • s time frame start, ISO or Unix timestamp
  • e time frame end, optional (default: current time), ISO or Unix timestamp
  • l limit history records (optional)
  • x item property (status or value)
  • t time format (iso or raw for Unix timestamp)
  • w fill frame with the specified interval (e.g. 1T - 1 minute, 2H - 2 hours etc.), optional
  • g output format, list (default) or dict

Returns state history for the chosen item(s) in the specified format.

To get state history for the multiple items:

  • w param is required
  • g should be specified as list

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found item doesn’t exist, the key has no access to the item, or the history database is not found

action - unit control actions

Create unit control action and put it into the queue of the controller.

Parameters:

  • k valid API key
  • i unique unit id
  • s new unit status
  • v new unit value

optionally:

  • p action priority in queue (the less value is** the higher priority is, default is 100)
  • u unique action id (use this option only if you know what you do, the system assigns the unique id by default)
  • w the API request will wait for the completion of the action for the specified number of seconds
  • q timeout (sec) for action processing in the public queue

Returns JSON dict with the following data (time** UNIX_TIMESTAMP):

{
   "err": "<output_stderr>",
   "exitcode": <exit_code>,
   "item_group": "<group>",
   "item_id": "<unit_id>",
   "item_type": "unit",
   "nstatus": <new_status>,
   "nvalue": "<new_value>",
   "out": "<output_stdout>",
   "priority": <priority>,
   "status": "<action_status>",
   "time": {
       "created": <creation_time>,
       "pending": <public_queue_pending_time>,
       "queued": <unit_queue_pending_time>,
       "running": <running_time>
   },
   "uuid": "<unique_action_id>"
}

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found unit doesn’t exist, or the key has no access to the unit

In case the parameter w is not present or action is not terminated in the specified wait time, it will continue running, and its status may be checked in with assigned uuid. If the action is terminated, out and err will have not null values and the process exit code will be available at exitcode. Additionally, time will be supplemented by completed, failed or terminated.

action_toggle - simple unit control

Create unit control action to switch its status between 0 and 1. Useful for simple units.

Parameters:

  • k valid API key
  • i unique unit id

optionally:

  • p action priority in queue (the less value is** the higher priority is, default is 100)
  • u unique action id (use this option only if you know what you do, the system assigns the unique id by default)
  • w the API request will wait for the completion of the action for the specified number of seconds
  • q timeout (sec) for action processing in the public queue

Returns and behavior:

Same as action

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found item doesn’t exist, or the key has no access to the item

result - get action status

Checks the result of the action by its UUID or returns the actions for the specified unit.

Parameters:

  • k valid API key
  • u action UUID or
  • i unit id

Additionally results may be filtered by:

  • g unit group
  • s action status (Q queued, R running, F finished)

Returns:

Same JSON dict as action

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found unit doesn’t exist, action with the specified UUID doesn’t exist, or the key has no access to them

terminate - terminate action

Terminate action execution or cancel the action if it’s still queued

Parameters:

  • k valid API key
  • u action UUID
  • i item id, either item id or action UUID must be specified

Returns:

Returns JSON dict result=”OK”, if the action is terminated. If the action is still queued, it will be canceled. result=”ERROR” may occur if the action termination is disabled in unit configuration.

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found item or action with the specified UUID doesn’t exist (or already completed), or the key has no access to it

q_clean - clean up the action queue

Cancel all queued actions, keep the current action running

Parameters:

  • k valid API key
  • i unit id

Returns JSON dict result=”OK”, if the queue is cleaned.

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found unit doesn’t exist, or the key has no access to it

kill - clean up the queue and terminate the actions

Apart from canceling all queued commands, this function also terminates the current running action.

Parameters:

  • k valid API key
  • i unit id

Returns JSON dict result=”OK”, if the command completed successfully. If the current action of the unit cannot be terminated by configuration, the notice “pt” = “denied” will be returned additionally (even if there’s no action running)

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found unit doesn’t exist, or the key has no access to it

disable_actions - disable actions for the unit

Disables unit to run and queue new actions.

Parameters:

  • k valid API key
  • i unit id

Returns JSON dict result=”OK”, if actions are disabled.

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found unit doesn’t exist, or the key has no access to it

enable_actions - enable actions for the unit

Enables unit to run and queue new actions.

Parameters:

  • k valid API key
  • i unit id

Returns JSON dict result=”OK”, if actions are enabled.

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found unit doesn’t exist, or the key has no access to it

update - set item status

Updates the status and value of the item. This is one of the ways of passive state update, for example with the use of an external controller. Calling without s and v params will force item to perform passive update requesting its status from script or driver.

Parameters:

  • k valid API key
  • i item id
  • s item status (integer, optional)
  • v item value (optional)

Returns JSON dict result=”OK”, if the state is updated successfully.

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found item doesn’t exist, or the key has no access to it

groups - get item group list

Get the list of item groups. Useful e.g. for custom interfaces.

Parameters:

  • k valid API key
  • p item type (U for unit, S for sensor), required

Returns JSON array:

[
    "parent_group1/group1",
    "parent_group1/group2"
]

Errors:

  • 403 Forbidden invalid API KEY

list - get item list

Returns the list of all items available

Parameters:

  • k masterkey

optionally:

  • p item type (U for unit, S for sensor)
  • g item group

Returns JSON array:

[
    {
        "description": "",
        "full_id": "item_group/item_id",
        "group": "item_group",
        "id": "item_id",
        "oid": "item_type:item_group/item_id",
        "type": "item_type"
    }

Errors:

  • 403 Forbidden invalid API KEY

get_config - get item configuration

Returns complete item configuration

Parameters:

  • k masterkey
  • i item id

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found item doesn’t exist, or the key has no access to it

save_config - save item configuration on disk

Saves item configuration on disk (even if it hasn’t been changed)

Parameters:

  • k masterkey
  • i item id

Returns JSON dict result=”OK”, if the configuration is saved successfully.

Errors:

  • 403 Forbidden invalid API KEY

list_props - get editable item parameters

Allows to get all editable parameters of the item configuration

Parameters:

  • k masterkey
  • i item id

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found item doesn’t exist, or the key has no access to it

set_prop - set item parameters

Allows to set configuration parameters of the item.

Parameters:

  • k masterkey
  • i item id
  • p item configuration param
  • v param value

Returns result=”OK” if the parameter is set, or result=”ERROR”, if an error occurs.

Errors:

  • 403 Forbidden invalid API KEY

create - create new item

Creates new item.

Parameters:

  • k masterkey
  • i item OID (type:group/id)

optionally:

  • virtual=1 unit is created as virtual
  • save=1 save unit configuration on disk immediately after creation

Returns result=”OK” if the item is created, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

create_unit - create new unit

Creates new unit.

Parameters:

  • k masterkey
  • i unit id
  • g unit group

optionally:

  • virtual=1 unit is created as virtual
  • save=1 save unit configuration on disk immediately after creation

Returns result=”OK” if the unit is created, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

create_sensor - create new sensor

Creates new sensor.

Parameters:

  • k masterkey
  • i sensor ID
  • g sensor group

optionally:

  • virtual=1 sensor is created as virtual
  • save=1 save sensor configuration on disk immediately after creation

Returns result=”OK” if the sensor is created, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

create_mu - create multiupdate

Creates new multiupdate.

Parameters:

  • k masterkey
  • i multiupdate ID
  • g multiupdate group

optionally:

  • virtual=1 multiupdate is created as virtual
  • save=1 save multiupdate configuration on disk immediately after creation

Returns result=”OK” if the multiupdate is created, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

clone - clone item

Creates a copy of the item.

Parameters:

  • k masterkey
  • i item ID
  • n new item ID
  • g group for the new item

optionally:

  • save=1 save item configuration on disk immediately after creation

Returns result=”OK” if the item is cloned, or result=”ERROR”, if an error occurred.

Note

Multiupdates can not be cloned

Errors:

  • 403 Forbidden invalid API KEY

clone_group - clone all items in the group

Creates a copy of all items from the group.

Parameters:

  • k masterkey
  • g group to clone
  • n new group to clone to
  • p item ID prefix, i.e. device1. for device1.temp1, device1.fan1
  • r iem ID prefix in the new group, i.e. device2

optionally:

  • save=1 save cloned items configurations on disk immediately after creation.

Returns result=”OK” if the items were cloned, or result=”ERROR”, if an error occurred. Only items with type unit and sensor are cloned.

Errors:

  • 403 Forbidden invalid API KEY

destroy - delete item or group

Deletes the item or the group (and all the items in it) from the system.

Parameters:

  • k masterkey
  • i item id
  • g item group (either id or group must be specified)

Returns result=”OK” if the item/group is deleted, or result=”ERROR”, if an error occurred.

Item configuration may be immediately deleted from the disk, if there is db_update=instant set in controller configuration, at the moment of shutdown, if there is db_update=on_exit, or when calling SYS API save (or save in UC EI), if there is db_update=manual.

If configuration is not deleted by either of these, you should delete it manually by removing the file runtime/uc_<type>.d/ID.json, otherwise the item(s) will remain in the system after restarting the controller.

Errors:

  • 403 Forbidden invalid API KEY

create_device - create device items

Creates the device from the specified template.

Parameters:

  • k key with allow=device permissions
  • c device config (var=value, comma separated or JSON dict)
  • t device template (runtime/tpl/TEMPLATE.json, without .json extension)

optionally:

  • save=1 save items configuration on disk immediately after operation

Returns result=”OK” if the item/group is deleted, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

update_device - update device items

Works similarly to create_device - create device items function but doesn’t create new items, updating the item configuration of the existing ones.

Parameters:

  • k key with allow=device permissions
  • c device config (var=value, comma separated or JSON dict)
  • t device template (runtime/tpl/TEMPLATE.json, without .json extension)

optionally:

  • save=1 save items configuration on disk immediately after operation

Returns result=”OK” if the item/group is deleted, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

destroy_device - destroy device items

Works in an opposite way to create_device - create device items function, destroying all items specified in the template.

Parameters:

  • k key with allow=device permissions
  • c device config (var=value, comma separated or JSON dict)
  • t device template (runtime/tpl/TEMPLATE.json, without .json extension)

Returns result=”OK” if the item/group is deleted, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

list_modbus_ports - list virtual ModBus ports

Returns a list which contains all virtual ModBus ports.

Parameters:

  • k masterkey

Errors:

  • 403 Forbidden invalid API KEY

create_modbus_port - create virtual ModBus port

Creates virtual ModBus port with the specified configuration.

Parameters:

  • k masterkey
  • i virtual port ID which will be used later in PHI configurations, required
  • p ModBus params, required
  • l=1 lock port on operations, which means to wait while ModBus port is used by other controller thread (driver command)
  • t ModBus operations timeout (in seconds, default: default timeout)
  • r retry attempts for each operation (default: no retries)
  • d delay between virtual port operations (default: 20ms)

Optionally:

  • save=1 save ModBus port config after creation

ModBus params should contain the configuration of hardware ModBus port. The following hardware port types are supported:

  • tcp , udp ModBus protocol implementations for TCP/IP networks. The params should be specified as: <protocol>:<host>[:port], e.g. tcp:192.168.11.11:502
  • rtu, ascii, binary ModBus protocol implementations for the local bus connected with USB or serial port. The params should be specified as: <protocol>:<device>:<speed>:<data>:<parity>:<stop> e.g. rtu:/dev/ttyS0:9600:8:E:1

Returns result=”OK” if port is created, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

test_modbus_port - verifies virtual ModBus port

Verifies virtual ModBus port by calling connect() ModBus client method.

Parameters:

  • k masterkey
  • i virtual port ID

Note

As ModBus UDP doesn’t require a port to be connected, API call always returns “OK” result.

Returns result=”OK” if port test is passed, or result=”ERROR”, if an error occurred.

destroy_modbus_port - delete virtual ModBus port

Deletes virtual ModBus port.

Parameters:

  • k masterkey
  • i virtual port ID

Returns result=”OK” if port is deleted, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

list_phi - list loaded PHIs

Returns a list which contains all loaded Physical Interfaces.

Parameters:

  • k masterkey

Errors:

  • 403 Forbidden invalid API KEY

load_phi - load PHI

Loads Physical Interface.

Parameters:

  • k masterkey
  • i PHI ID, required
  • m PHI module, required
  • c PHI configuration

Optionally:

  • save=1 save driver configuration after successful call

Returns a dict with information about PHI if module is loaded, or result=”ERROR”, if an error occurred.

Note

After successful load PHI automatically creates a driver with ID <PHI_ID>.default

Errors:

  • 403 Forbidden invalid API KEY

unload_phi - unload PHI

Unloads PHI. PHI should not be used by any driver (except default, but the driver should not be in use by any item).

Parameters:

  • k masterkey
  • i PHI ID

Returns result=”OK” if module is unloaded, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

put_phi_mod - upload PHI module

Allows to upload new PHI module to xc/drivers/phi folder.

Parameters:

  • k masterkey
  • m PHI module name (without .py extension)
  • c PHI module content (as-is)

Optionally:

  • force==1 overwrite PHI module file if exists

Returns result=”OK” if module is uploaded, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

get_phi - get loaded PHI information

Returns a dict with information about PHI

Parameters:

  • k masterkey
  • i PHI ID

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Forbidden PHI not found
  • 500 Internal Error inaccessible PHI

test_phi - test PHI

Returns PHI test result. All PHIs respond to self command, help command returns all available test commands.

Parameters:

  • k masterkey
  • i PHI ID
  • c test command

Returns test result. self command always returns a JSON string (not a dict), either OK or FAILED.

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Forbidden PHI not found
  • 500 Internal Error inaccessible PHI or PHI test is failed

exec_phi - execute additional PHI commands

Returns PHI command execution result. help command returns all available commands.

Parameters:

  • k masterkey
  • i PHI ID
  • c command to exec

Returns command execution result (usually strings OK, FAILED or not implemented)

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Forbidden PHI not found
  • 500 Internal Error inaccessible PHI or PHI test is failed

list_phi_mods - get list of available PHI modules

Returns a list of all available PHI modules.

Parameters:

  • k masterkey

Errors:

  • 403 Forbidden invalid API KEY

modinfo_phi - get PHI module info

Returns a dict with information about PHI module.

Parameters:

  • k masterkey
  • m PHI module

Errors:

  • 403 Forbidden invalid API KEY
  • 500 Internal Error inaccessible PHI module

modhelp_phi - get PHI module usage help

Returns a dict with PHI usage help.

Parameters:

  • k masterkey
  • m PHI module
  • c help context (cfg, get or set)

Errors:

  • 403 Forbidden invalid API KEY
  • 500 Internal Error inaccessible PHI module

list_drivers - list loaded drivers

Returns a list of loaded drivers

Parameters:

  • k masterkey

Errors:

  • 403 Forbidden invalid API KEY

load_driver - load driver

Loads a driver, combining previously loaded PHI and chosen LPI module.

Parameters:

  • k masterkey
  • i LPI ID
  • m LPI module
  • p PHI ID
  • c Driver (LPI) configuration, optional

Optionally:

  • save=1 save driver configuration after successful call

Note

Driver ID is a combination of PHI and LPI IDs: DRIVER_ID = PHI_ID.LPI_ID

Returns result=”OK” if driver is loaded, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

get_driver - get loaded driver information

Parameters:

  • k masterkey
  • i PHI ID

Returns a dict with information about driver (both LPI and PHI)

Errors:

  • 403 Forbidden invalid API KEY
  • 500 Internal Error inaccessible driver

unload driver - unload driver

Unloads specified driver (LPI only, leaving PHI untouched)

Parameters:

  • k masterkey
  • i Driver ID

Returns result=”OK” if driver is unloaded, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

list_lpi_mods - get list of available LPI modules

Returns a list of all available LPI modules.

Parameters:

  • k masterkey

Errors:

  • 403 Forbidden invalid API KEY

modinfo_lpi - get LPI module info

Returns a dict with information about LPI module.

Parameters:

  • k masterkey
  • m LPI module

Errors:

  • 403 Forbidden invalid API KEY
  • 500 Internal Error inaccessible LPI module

modhelp_lpi - get LPI module usage help

Returns a dict with LPI usage help.

Parameters:

  • k masterkey
  • m LPI module
  • c help context (cfg, action or update)

Errors:

  • 403 Forbidden invalid API KEY
  • 500 Internal Error inaccessible LPI module

set_driver - set driver to item

Sets the specified driver to item, automatically updating item props:

  • action_driver_config, update_driver_config to the specified configuration
  • action_exec, update_exec to do all operations via driver function calls (sets both to |<driver_id>)

Parameters:

  • k masterkey
  • i item ID
  • d driver ID (if none - all above item props are set to null)
  • c configuration (e.g. port number)

Optionally:

  • save=1 save item configuration after successful call

Returns result=”OK” if driver is set, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

User authorization using login/password

Third-party apps may authorize users using login and password as an alternative for authorization via API key.

login - user authorization

Authorizes user in the system and and opens a new authorized session. Session ID is stored in cookie.

Attention! Session is created for all requests to API, even if login is not used; web-browsers use the same session for the host even if apps are running on different ports. Therefore, when you use web-apps (even if you use the same browser to simultaneously access system interfaces or other apps) each app/interface should be associated with different domains/alias/different host IP addresses.

Parameters:

  • u user name
  • p user password

Returns JSON dict { “result” “OK”, “key”: “APIKEY_ID” }, if the user is authorized.

Errors:

  • 403 Forbidden invalid user name / password
logout

Finishes the authorized session

Parameters: none

Returns JSON dict { “result” : “OK” }

Errors:

  • 403 Forbidden no session available / session is already finished

info - reserved

Internal function, reserved for future use.

Parameters: none (no API key required).

Returns: component information.

UDP API

UDP API enables to call API action and update functions by sending a simple UDP packet.

Basics

As there is no feedback in UDP, it is not recommended to use UDP API in cases where reliability is critical, but its usability for programmable microcontrollers sometimes takes advantage.

To update the status of the item send the following UDP packet to API port:

<ID> u <status> [value]

(ID - item id, value - optional parameter).

To send action for the unit send the following UDP packet to API port:

<ID> <status> [value] [priority]

(value and priority** optional parameters).

If you needs to skip the parameter, set it to ‘None’. For example:

sensor1 u None 29.55

will keep sensor1 status and set value 29.55;

or

unit1 1 None 50

will run the action for unit1 for changing its status to 1, without changing the value, with priority 50.

Batch commands

You can specify multiple commands in one packet separating them with NL (n) symbol. Example:

sensor1 u 1 29.55
sensor2 u 1 26
sensor3 u 1 38
Encryption and authentication

You may specify in controller configuration to accept only encrypted packets from the specified hosts or networks. By default it’s recommended to accept unencrypted packets without authentication only in trusted networks. The packet is encrypted and signed with API key and can not be decrypted and used without having it, so API key acts both for encryption and authentication.

Encrypted packet format is:

|KEY_ID|ENCRYPTED_DATA

Where KEY_ID is API key ID and ENCRYPTED_DATA - UDP API packet (which may contain either single or multiple commands at once). The data is encrypted using Fernet - a symmetric encryption method which uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256 for authentication.

Fernet requires 32-bit base64-encoded key, so before data encryption, API key should be converted with the following: base64encode(sha256sum(api_key)).

Python example:

import hashlib
import base64

from cryptography.fernet import Fernet

api_key = 'mysecretapikey'
data = 'sensor1 u 1 29.55'

encryption_key = base64.b64encode(hashlib.sha256(api_key.encode()).digest())
ce = Fernet(encryption_key)

result = ce.encrypt(data.encode())

Fernet implementation is simple and pre-made libraries are available for all major programming languages.

Custom packets

You can send a custom packet to let it be parsed by loaded PHI.

Custom packet format is (\x = hex):

\x01 HANDLER_ID \x01 DATA

DATA is always transmitted to handler in binary format. Encryption, authentication and batch commands in custom packets are not supported.

LM API

Logic Manager LM API is called through URL request

http://<IP_address_LM:Port>/lm-api/function

If SSL is allowed in the controller configuration file, you can also use https calls.

All functions can be called using GET and POST methods. When POST method is used, the parameters can be passed to functions either as www-form or as JSON.

Note

Object creation and modification functions don’t save configurations automatically unless you specify save parameter in API request. The system is designed to work this way to let you discard the changes in case of serious problems by killing the controller process.

If you need to save any changes made without this parameter, restart the controller gracefully or use SYS API save function.

Contents

test - test API/key and get system info

Test can be executed with any valid API KEY

Parameters:

  • k valid API key

Returns JSON dict with system info and current API key permissions (for masterkey only ‘master’:true is returned)

{
    "acl": {
        "allow": {
            "cmd": true
        },
        "groups": [
            "system/#",
            "service",
            "security/+"
        ],
        "items": [],
        "key_id": "key1",
        "master": false,
        "sysfunc": false
    },
    "product_build": 2017082101,
    "product_code": "lm",
    "product_name": "EVA Logic Manager",
    "result": "OK",
    "system": "eva3-test1",
    "time": 1504489043.4566338,
    "version": "3.0.0"
}

Errors:

  • 403 Forbidden the key has no access to the API

state - get logic variable state

State of the lvar or all logic variables can be obtained using state command.

Parameters:

  • k valid API key
  • i lvar ID
  • g group filter, optional mqtt masks can be used, for example group1/#, group1/+/lamps)
  • full=1 display extended item info, optional (config_changed, description, virtual, status_labels and action_enabled for unit)

Returns lvar status in JSON dict or array of dicts:

[
    {
        "expires": 0,
        "full_id": "service/test",
        "group": "service",
        "id": "test",
        "set_time": 1506345719.8540998,
        "status": 1,
        "type": "lvar",
        "value": "33"
    }
]

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found lvar doesn’t exist, or the key has no access to the lvar

state_history - get item state history

State history of one item or several items of the specified type can be obtained using state_history command.

Parameters:

  • k valid API key
  • i item ID, or multiple IDs, comma separated
  • a notifier ID which keeps history for the specified item(s) (default: db_1)
  • s time frame start, ISO or Unix timestamp
  • e time frame end, optional (default: current time), ISO or Unix timestamp
  • l limit history records (optional)
  • x item property (status or value)
  • t time format (iso or raw for Unix timestamp)
  • w fill frame with the specified interval (e.g. 1T - 1 minute, 2H - 2 hours etc.), optional
  • g output format, list (default) or dict

Returns state history for the chosen item(s) in the specified format.

To get state history for multiple items:

  • w param is required
  • g should be specified as list

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found item doesn’t exist, the key has no access to the item, or the history database is not found

set - set lvar state

Allows to set status and value of a logic variable.

Parameters:

  • k valid API key
  • i lvar id
  • s lvar status, optional
  • v lvar value, optional

Returns JSON dict result=”OK”, if the state is set successfully.

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found lvar doesn’t exist, or the key has no access to the lvar

reset - reset lvar state

Allows to set status and value of a logic variable to 1. Useful when lvar is being used as a timer to reset it, or as a flag to set it True.

Parameters:

  • k valid API key
  • i lvar id

Returns JSON dict result=”OK”, if the state is reset successfully.

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found lvar doesn’t exist, or the key has no access to the lvar

clear - clear lvar state

Allows to set status (if expires lvar param > 0) or value (if expires isn’t set) of a logic variable to 0. Useful when lvar is used as a timer to stop it, or as a flag to set it False.

Returns JSON dict result=”OK”, if the state is cleared successfully.

Parameters:

  • k valid API key
  • i lvar id

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found lvar doesn’t exist, or the key has no access to the lvar

toggle - toggle lvar value

Allows to switch value of a logic variable between 0 and 1. Useful when lvar is being used as a flag to switch it between True/False.

Parameters:

  • k valid API key
  • i lvar id

Returns JSON dict result=”OK”, if the state is toggled successfully.

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found lvar doesn’t exist, or the key has no access to the lvar

groups - get lvar groups list

Get the list of the lvar groups. Useful i.e. for custom interfaces.

Parameters:

  • k valid API key

Returns JSON array:

[
    "parent_group1/group1",
    "parent_group1/group2"
]

Errors:

  • 403 Forbidden invalid API KEY

groups_macro - get macro groups list

Get the list of macro groups.

Parameters:

  • k valid API key

Returns JSON array:

[
    "parent_group1/group1",
    "parent_group1/group2"
]

Errors:

  • 403 Forbidden invalid API KEY

list_macros - get macro list

Get the list of all available macros.

Parameters:

  • k valid API key
  • g filter by group, optional (MQTT masks may be used, i.e. group1/#, group1/+/service)

Returns JSON array:

[
    {
       "action_enabled": true,
       "description": "description",
       "full_id": "group/macro_id",
       "group": "group",
       "id": "macro_id",
       "oid": "lmacro:group/macro_id",
       "type": "lmacro"
    }
]

Errors:

  • 403 Forbidden invalid API KEY

run - execute macro

Executes a macro with the specified arguments.

Parameters:

  • k valid API key
  • i macro id

optionally:

  • a macro arguments, space separated
  • p queue priority (less value - higher priority, default 100)
  • u unique action id (use this option only if you know what you do, the system assigns the unique ID by default)
  • w the API request will wait for the completion of the action for the specified number of seconds
  • q timeout (sec) for action processing in the public queue

Returns JSON dict with the following data (time** UNIX_TIMESTAMP):

{
   "err": "<compilation and exec errors>",
   "exitcode": <exit_code>,
   "item_group": "<group>",
   "item_id": "<macro_id>",
   "item_type": "lmacro",
   "out": "[macro out variable]",
   "priority": <priority>,
   "status": "<action status>",
   "time": {
       "created": <creation_time>,
       "pending": <public_queue_pending_time>,
       "queued": <controller_queue_pending_time>,
       "running": <running_time>
   },
   "uuid": "unique_action_id"
}

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found macro doesn’t exist, or the key has no access to the macro

In case the parameter w is not indicated or action is not finished in the specified time, it should continue running, and its status may be checked in accordance with assigned uuid. If action is terminated, exit code will stand for the exit code of the macro. Additionally, time will be supplemented by completed, failed or terminated. out field contains the output of out variable (if it was associated with a value in the macro), err field (in case of macro compilation/execution errors)contains the error details.

result - macro execution result

Get macro execution results either by action uuid or by macro id.

Parameters:

  • k valid API key

optionally:

  • i macro_id
  • u action uuid (either action uuid or macro_id must be specified)
  • g filter by macro group
  • s filter by action status (Q - queued, R - running, F - finished)

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found macro or action doesn’t exist, or the key has no access to the macro

Actions remain in the system until they receive the status completed, failed or terminated and until keep_action_status time indicated in controller configuration passes.

Note

This function doesn’t return results of the unit actions executed by macros

list - get list of all logic variables

Parameters:

  • k masterkey API key
  • g filter by group (optional)

Returns JSON array which contains lvars:

[
    {
       "description": "description",
       "expires": 0,
       "full_id": "group/id",
       "group": "group",
       "id": "lvare_id",
       "oid": "lvar:group/id",
       "set_time": 9999999,
       "type": "lvar"
    }
]

Errors:

  • 403 Forbidden invalid API KEY

get_config - get logic variable configuration

Parameters:

  • k masterkey
  • i lvar id

Returns complete lvar configuration.

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found lvar doesn’t exist

save_config - save lvar configuration on disk

Saves lvar configuration on disk (even if it wasn’t changed)

Parameters:

  • k masterkey
  • i lvar id

Returns JSON dict result=”OK”, if the configuration is saved successfully.

Errors:

  • 403 Forbidden invalid API KEY

list_props - get editable lvar parameters

Allows to get all editable parameters of the lvar configuration.

Parameters:

  • k masterkey
  • i lvar id

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found lvar doesn’t exist

set_prop - set item parameters

Allows to set configuration parameters of the lvar.

Parameters:

  • k masterkey
  • i lvar id
  • p lvar configuration param
  • v param value

Returns result=”OK” if the parameter is set, or result=”ERROR”, if an error occurs.

Errors:

  • 403 Forbidden invalid API KEY

create_lvar - create new lvar

Creates new lvar.

Parameters:

  • k masterkey
  • i lvar id
  • g lvar group

optionally:

  • save=1 save lvar configuration on the disk immediately after creation

Returns result=”OK” if the lvar is created, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

destroy_lvar - delete lvar

Deletes lvar.

Parameters:

  • k masterkey
  • i lvar id

Returns result=”OK” if the lvar is deleted, or result=”ERROR”, if an error occurred.

LVar configuration can be immediately deleted from the disk, if there is db_update=instant set in controller configuration, at the moment of shutdown, if there is db_update=on_exit, or when calling SYS API save (or save in LM EI), if there is db_update=manual.

If configuration is not deleted by either of these, you should delete it manually by removing the file runtime/lm_lvar.d/ID.json, otherwise the lvar(s) will remain in the system after restarting the controller.

Errors:

  • 403 Forbidden invalid API KEY

list_macro_props - get editable macro parameters

Allows to get all editable parameters of the macro configuration.

Parameters:

  • k masterkey
  • i macro id

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found macro doesn’t exist

set_macro_prop - set macro parameters

Allows to set configuration parameters of the macro.

Parameters:

  • k masterkey
  • i macro id
  • p macro configuration param
  • v param value

Returns result=”OK” if the parameter is set, or result=”ERROR”, if an error occurs.

Errors:

  • 403 Forbidden invalid API KEY

create_macro - create new macro

Creates new macro. Macro code should be put in xc/lm manually.

Parameters:

  • k masterkey
  • i macro id
  • g macro group

optionally:

  • save=1 save macro configuration on the disk immediately after creation

Returns result=”OK” if a macro is created, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

destroy_macro - delete macro

Deletes macro.

Parameters:

  • k masterkey
  • i macro id

Returns result=”OK” if the macro is deleted, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

list_rules - get rules list

Get the list of all available decision rules.

Parameters:

  • k valid API key

Returns JSON array of rules and their properties.

Errors:

  • 403 Forbidden invalid API KEY

list_rule_props - get editable rule parameters

Allows to get all editable parameters of the decision rule.

Parameters:

  • k masterkey or a key with allow=dm_rule_props to access in_range_*,_**enabled** and chillout_time rule props, or with an access to a certain rule by ID
  • i rule id

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found rule doesn’t exist

set_rule_prop - set rule parameters

Allows to set configuration parameters of the rule.

Parameters:

  • k masterkey or a key with allow=dm_rule_props to access in_range,_*enabled and chillout_time rule settings, or with an access to a certain rule
  • i rule id
  • p rule configuration param
  • v param value

Returns result=”OK” if the parameter is set, or result=”ERROR”, if an error occurs.

Errors:

  • 403 Forbidden invalid API KEY

create_rule - create new rule

Creates new decision rule. Rule id is always generated automatically.

Parameters:

  • k masterkey

optionally:

  • save=1 save rule configuration on the disk immediately after creation

Returns result=”OK” if a rule is created, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

destroy_rule - delete rule

Deletes decision rule.

Parameters:

  • k masterkey
  • i rule id

Returns result=”OK” if the rule is deleted, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

list_remote - get a list of items from connected UCs

Get a list of the items loaded from the connected UC controllers. Useful to debug the controller connections.

Parameters:

  • k masterkey

optionally:

  • g item group
  • p item type (U for unit, S for sensor)

Returns the JSON array of units and sensors loaded from the remote controllers. Additional field controller_id is present in any item indicating the controller it’s loaded from.

Errors:

  • 403 Forbidden invalid API KEY

list_controllers - get controllers list

Get the list of all connected UC controllers.

Parameters:

  • k valid API key

Returns JSON array:

[
    {
    "build": "BUILD",
    "connected": true,
    "description": "<controller_description>",
    "full_id": "<uc/controller_id>",
    "group": "uc",
    "id": "<controller_id>",
    "oid": "<remote_uc:uc/controller_id>",
    "type": "remote_uc",
    "version": "VERSION"
    }
]

Errors:

  • 403 Forbidden invalid API KEY

test_controller - test connection to remote controller

Allows to test connection to the UC controller.

  • k masterkey
  • i controller id

Returns result=”OK” if the test is passed, or result=”ERROR”, if failed.

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found controller doesn’t exist

list_controller_props - get editable controller parameters

Allows to get all editable parameters of the connected UC controller.

  • k masterkey
  • i controller id

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found controller doesn’t exist

set_controller_prop - set controller parameters

Allows to set configuration parameters of the connected UC.

Parameters:

  • k masterkey
  • i controller id
  • p controller configuration param
  • v param value

Returns result=”OK” if the parameter is set, or result=”ERROR”, if an error occurs.

Errors:

  • 403 Forbidden invalid API KEY

append_controller - connect remote UC

Connects remote UC controller to the local.

Parameters:

  • k masterkey
  • uri UC API uri (proto://host:port)
  • a remote controller API key ($key to use local key)

optionally:

  • m MQTT notifier to exchange item states in real time
  • s True/False (1/0) verify remote SSL certificate or pass invalid
  • t timeout (seconds) for the remote controller API calls
  • save=1 save connected controller configuration on the disk immediately after creation

Returns result=”OK” if the controller is connected, or result=”ERROR”, if an error occurred.

The remote controller id is obtained and set automatically according to its hostname or name field in the controller configuration. The remote controller id can’t be changed.

Errors:

  • 403 Forbidden invalid API KEY

remove_controller - disconnect UC

Disconnects the remote UC controller.

Parameters:

  • k masterkey
  • i controller id

Returns result=”OK” if the controller is disconnected, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

reload_controller - reload items from UC

Allows to immediately reload all the items and their status from the remote UC controller.

Parameters:

  • k masterkey
  • i controller id

Returns result=”OK” if the controller is deleted, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

list_ext - list loaded macro extensions

Returns a list which contains all loaded macro extensions.

Parameters:

  • k masterkey

Errors:

  • 403 Forbidden invalid API KEY

load_ext - load macro extension

Loads:doc:macro extension</lm/ext>.

Parameters:

  • k masterkey
  • i macro extension ID, required
  • m macro extension module, required
  • c macro extension configuration

Optionally:

  • save=1 save extension configuration after successful call

Returns a dict with information about extension if module is loaded, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

unload_ext - unload macro extension

Unloads macro extension.

Parameters:

  • k masterkey
  • i macro extension ID

Returns result=”OK” if module is unloaded, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

get_ext - get loaded macro extension information

Returns a dict with information about macro extension

Parameters:

  • k masterkey
  • i macro extension ID

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Forbidden macro extension not found
  • 500 Internal Error inaccessible macro extension

list_ext_mods - get list of available macro extension modules

Returns a list of all available macro extension modules.

Parameters:

  • k masterkey

Errors:

  • 403 Forbidden invalid API KEY

modinfo_ext - get macro extension module info

Returns a dict with information about macro extension module.

Parameters:

  • k masterkey
  • m macro extension module

Errors:

  • 403 Forbidden invalid API KEY
  • 500 Internal Error inaccessible macro extension module

modhelp_ext - get extension module usage help

Returns a dict with macro extension usage help.

Parameters:

  • k masterkey
  • m macro extension module
  • c help context (cfg or functions)

Errors:

  • 403 Forbidden invalid API KEY
  • 500 Internal Error inaccessible macro extension module

User authorization using login/password

Third-party apps may authorize users using login and password as an alternative for authorization via API key.

login - user authorization

Authorizes user in the system and and opens a new authorized session. Session ID is stored in cookie.

Attention! Session is created for all requests to API, even if login is not used; web-browsers use the same session for the host even if apps are running on different ports. Therefore, when you use web-apps (even if you use the same browser to simultaneously access system interfaces or other apps) each app/interface should be associated with different domains/alias/different host IP addresses.

Parameters:

  • u user name
  • p user password

Returns JSON dict { “result” “OK”, “key”: “APIKEY_ID” }, if the user is authorized.

Errors:

  • 403 Forbidden invalid user name / password
logout

Finishes the authorized session

Parameters: none

Returns JSON dict { “result” : “OK” }

Errors:

  • 403 Forbidden no session available / session is already finished

info - reserved

Internal function, reserved for future use.

Parameters: none (no API key required).

Returns: component information.

SFA API

SCADA Final Aggregator SFA API is called through URL request

http://<IP_address_SFA:Port>/sfa-api/function

If SSL is allowed in the controller configuration file, you can also use https calls.

All functions can be called using GET and POST methods. When POST method is used, the parameters can be passed to functions either as www-form or as JSON.

Note

Object creation and modification functions don’t save configurations automatically unless you specify save parameter in API request. The system is designed to work this way to let you discard the changes in case of serious problems by killing the controller process.

If you need to save any changes made without this parameter, restart the controller gracefully or use SYS API save function.

Functions passed to the remote controllers

The following functions are passed to the connected remote controllers and return the result as-is.

Note

Function result is used to return both unit action results and macro execution results.

Units control
Logic variables control
Macros control
  • run - LM API call to execute logic control macro
  • result - LM API call to get macro execution result by macro oid (lmacro/group/id) or uuid
Decision rules control

test - test API/key and get system info

Test can be executed with any valid API KEY

Parameters:

  • k valid API key

Returns JSON dict with system info and current API key permissions (for masterkey only ‘master’:true is returned)

{
    "acl": {
        "allow": {
            "cmd": true
        },
        "groups": [
            "system/#",
            "service",
            "security/+"
        ],
        "items": [],
        "key_id": "key1",
        "master": false,
        "sysfunc": false
    },
    "product_build": 2017082101,
    "product_code": "sfa",
    "product_name": "EVA SCADA Final Aggregator"
    "result": "OK",
    "system": "eva3-test1",
    "time": 1504489043.4566338,
    "version": "3.0.0"
}

Errors:

  • 403 Forbidden the key has no access to the API

reload_clients - ask connected clients to reload

This function sends reload event to all connected clients asking them to reload the interface.

All the connected clients receive the event with subject=”reload” and data=”asap”. If the clients use SFA Framework, they must define eva_sfa_reload_handler function.

Parameters:

  • k masterkey

Returns result=”OK” if the reload event is sent, or result=”ERROR”, if an error occurs.

Errors:

  • 403 Forbidden invalid API KEY

notify_restart - notify about server restart

This function sends a server restart event to all connected clients asking them to prepare for server restart.

All the connected clients receive the event with subject=”server” and data=”restart”. If the clients use SFA Framework, they must define eva_sfa_server_restart_handler function.

Server restart notification is sent automatically to all connected clients when the server is restarting. This API function allows to send server restart notification without actual server restart, which may be useful e.g. for testing, handling frontend restart etc.

Parameters:

  • k masterkey

Returns result=”OK” if the reload event is sent, or result=”ERROR”, if an error occurs.

Errors:

  • 403 Forbidden invalid API KEY

state - get item state

State of the known item or all the items of the specified type can be obtained using state command.

Parameters:

  • k valid API key
  • p item type (U for unit, S for sensor, LV for lvar, required
  • i full item id (group/id), optional
  • g group filter, optional mqtt masks can be used, for example group1/#, group1/+/lamps) virtual, status_labels and action_enabled for unit)

optionally:

  • full=1 get full item state info (description, action labels and etc.)

Returns item status in JSON dict or array of dicts:

[
    {
        "expires": 0,
        "full_id": "service/test",
        "group": "service",
        "id": "test",
        "set_time": 1506345719.8540998,
        "status": 1,
        "type": "lvar",
        "value": "33"
    }
]

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found item doesn’t exist, or the key has no access to the item

state_history - get item state history

State history of one item or several items of the specified type can be obtained using state_history command.

Parameters:

  • k valid API key
  • i item ID, or multiple IDs, comma separated
  • a notifier ID which keeps history for the specified item(s) (default: db_1)
  • s time frame start, ISO or Unix timestamp
  • e time frame end, optional (default: current time), ISO or Unix timestamp
  • l limit history records (optional)
  • x item property (status or value)
  • t time format (iso or raw for Unix timestamp)
  • w fill frame with the specified interval (e.g. 1T - 1 minute, 2H - 2 hours etc.), optional
  • g output format, list (default) or dict

Returns state history for the chosen item(s) in the specified format.

To get state history for multiple items:

  • w param is required
  • g should be specified as list

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found item doesn’t exist, the key has no access to the item, or the history database is not found

groups - get item group list

Get the list of the item groups. Useful e.g. for custom interfaces.

Parameters:

  • k valid API key
  • p item type (U for unit, S for sensor, LV for lvar), required

optionally:

Returns JSON array:

[
    "parent_group1/group1",
    "parent_group1/group2"
]

Errors:

  • 403 Forbidden invalid API KEY

groups_macro - get macro groups list

Get the list of the macro groups.

Parameters:

  • k valid API key

Returns JSON array:

[
    "parent_group1/group1",
    "parent_group1/group2"
]

Errors:

  • 403 Forbidden invalid API KEY

list_macros - get macro list

Get the list of all available macros.

Parameters:

  • k valid API key
  • g filter by group, optional (MQTT masks may be used, i.e. group1/#, group1/+/service)

Returns JSON array:

[
    {
       "action_enabled": true,
       "description": "description",
       "full_id": "group/macro_id",
       "group": "group",
       "id": "macro_id",
       "oid": "lmacro:group/macro_id",
       "type": "lmacro"
    }
]

Errors:

  • 403 Forbidden invalid API KEY

list_remote - get a list of items from connected controllers

Get a list of the items loaded from the connected controllers. Useful to debug the controller connections.

Parameters:

  • k masterkey

optionally:

Returns the JSON array of units, sensors loaded from the remote controllers. Additional field controller_id is present in any item indicating the controller it’s loaded from.

Errors:

  • 403 Forbidden invalid API KEY

list_controllers - get controllers list

Get the list of all connected controllers.

Parameters:

  • k valid API key
  • g controller type (uc or lm, optional)

Returns JSON array:

[
    {
    "build": "BUILD",
    "connected": true,
    "description": "<controller_description>",
    "full_id": "<type/controller_id>",
    "group": "<type>",
    "id": "<controller_id>",
    "oid": "remote_<type>:<type>/<controller_id>",
    "type": "remote_<type>",
    "version": "VERSION"
    }
]

Errors:

  • 403 Forbidden invalid API KEY

test_controller - test connection to remote controller

Allows to test connection to the controller.

  • k masterkey
  • i controller id

Returns result=”OK” if the test is passed, or result=”ERROR”, if failed.

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found controller doesn’t exist

list_controller_props - get editable controller parameters

Allows to get all editable parameters of the connected controller.

  • k masterkey
  • i controller id

Errors:

  • 403 Forbidden invalid API KEY
  • 404 Not Found controller doesn’t exist

set_controller_prop - set controller parameters

Allows to set configuration parameters of the connected controller.

Parameters:

  • k masterkey
  • i controller id
  • p controller configuration param
  • v param value

Returns result=”OK” if the parameter is set, or result=”ERROR”, if an error occurs.

Errors:

  • 403 Forbidden invalid API KEY

append_controller - connect remote controller

Connects remote controller to the local.

Parameters:

  • k masterkey
  • uri API uri (proto://host:port)
  • a remote controller API key ($key to use local key)

optionally:

  • m MQTT notifier to exchange item states in real time
  • s True/False (1/0) verify remote SSL certificate or pass invalid
  • t timeout (seconds) for the remote controller API calls
  • save=1 save connected controller configuration on the disk immediately after creation

Returns result=”OK” if the controller is connected, or result=”ERROR”, if an error occurred.

The remote controller id is obtained and set automatically according to its hostname or name field in the controller configuration. The remote controller id can’t be changed.

Errors:

  • 403 Forbidden invalid API KEY

remove_controller - disconnect remote controller

Disconnects the remote controller.

Parameters:

  • k masterkey
  • i controller id

Returns result=”OK” if the controller is disconnected, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

reload_controller - reload items from UC

Allows to immediately reload all the items, macros and their status from the remote controller.

Parameters:

  • k masterkey
  • i controller id

Returns result=”OK” if the controller is deleted, or result=”ERROR”, if an error occurred.

Errors:

  • 403 Forbidden invalid API KEY

User authorization using login/password

Third-party apps may authorize users using login and password as an alternative for authorization via API key.

login - user authorization

Authorizes user in the system and and opens a new authorized session. Session ID is stored in cookie.

Attention! Session is created for all requests to API, even if login is not used; web-browsers use the same session for the host even if apps are running on different ports. Therefore, when you use web-apps (even if you use the same browser to simultaneously access system interfaces or other apps) each app/interface should be associated with different domains/alias/different host IP addresses.

Parameters:

  • u user name
  • p user password

Returns JSON dict { “result” “OK”, “key”: “APIKEY_ID” }, if the user is authorized.

Errors:

  • 403 Forbidden invalid user name / password
logout

Finishes the authorized session

Parameters: none

Returns JSON dict { “result” : “OK” }

Errors:

  • 403 Forbidden no session available / session is already finished

API Clients

All EVA API servers were designed as a simple and user-friendly way to work from a command line using traditional calling methods of Linux http requests: GET, wget, lynx, curl etc. That is why JSON incoming data is always duplicated with the traditional GET/POST request parameters.

API outgoing data is always in JSON format, which can easily be parsed with the use of jq (usually available in all modern Linux distributions), for example:

curl -s 'http://localhost:8812/uc-api/test?k=APIKEY' | jq .version -r

You may call API functions via HTTP GET or HTTP POST - all functions respond similarly regardless of the request method. You may use www-form as well as JSON for POST.

If you want to integrate EVA API in your Python or PHP application, EVA can offer ready-made client libraries.

API client for Python

API client for Python has already been installed on all EVA servers and is used by the system itself. If you want to use the client module on another system, just create eva/client folder, copy lib/eva/client/apiclient.py file into it and create an empty file __init__.py. Other files in this folder are used by the system for a higher level of access to API, which is not publicly documented.

Example of working with API from Python is located in include/python/ folder of EVA.

API classes for Python

API has two classes:

eva.client.apiclient.APIClient()

and

eva.client.apiclient.APIClientLocal(product, dir_eva=None)

APIClientLocal class may be used on the servers where EVA is installed. When specifying product=’<subsystem code>’ parameter (i.e. product = ‘uc’) API is automatically initialized by loading parameters and keys specifically from configuration files of the controller. If you load the apiclient.py module from lib/eva/client/ folder, it is not necessary to set dir_eva parameter. Otherwise, it should point to EVA root folder.

APIClient class should always be initialized manually.

API requires jsonpickle and requests modules.

API initialization

API is initialized with the use of the following functions:

  • set_key(key) set API key.
  • set_uri(uri) set the root API URI (i.e., http://localhost:8812). You don’t need to specify the full path to API.
  • set_timeout(timeout) set maximum request timeout (seconds), 5 seconds by default.
  • set_product(product) set controller type: uc for Universal Controller, lm for Logic Manager, sfa for SCADA Final Aggregator. The client automatically identifies which API is to be called - either SYS API one or the one of the certain controller - that is why this parameter is required.
  • ssl_verify(v) to verify or not SSL certificate validity while working through https://. Can be True (check) or False (don’t check). The certificate is verified by default.

Example:

from eva.client.apiclient import APIClient
api = APIClient()
api.set_key(APIKEY)
api.set_uri('http://192.168.0.77:8812')
api.set_product('uc')
API function call

API functions are invoked by calling the call function:

APIClient.call(func, params=None, timeout=None)

where:

  • params the dict of the request parameters (if required)
  • timeout - maximum time (in seconds) to wait for the API response (if not set - the default timeout is used or the one set during API client initialization).

Example:

from eva.client.apiclient import APIClientLocal
api = APIClientLocal('uc')
code, result = api.call('state', { 'i': 'unit1' })

The function returns a tuple of two variables:

  • code API call result
  • result the result itself (JSON response converted to Python dict or array).
API result codes

Result codes are stored in module variables (i.e. apiclient.result_ok)

# the call succeeded
result_ok = 0
# the item is not found or the function requires a different set of
# parameters
result_not_found = 1
# access is denied with the set key
result_forbidden = 2
# - API error, e.g. the string param was used instead of a number
result_api_error = 3
# unknown error: all errors not listed here fall within this category
result_unknown_error = 4
# API is not initialized - URI is not set
result_not_ready = 5
# Attempt to call API function unknown to the client
result_func_unknown = 6
# server connection failed
result_server_error = 7
# the server request exceeded the time set in timeout
result_server_timeout = 8
# API returned data not in JSON or it cannot be parsed
result_bad_data = 9
# action failed (e.g., when calling  SYS API cmd or UC API action functions)
result_func_failed = 10
# the function is called with invalid params
result_invalid_params = 11

API client for PHP

API client for PHP has already been installed on all EVA servers. If you want to use the client library on another system, just copy include/php/eva-apiclient.php file.

Example of working with API from PHP is located in include/php/ folder of EVA.

API classes for PHP

API has two classes:

<?php EVA_APIClient(); ?>

and

<?php EVA_APIClientLocal($product, $dir_eva); ?>

EVA_APIClientLocal class may be used on the servers where EVA is installed. When specifying product=’<subsystem code>’ parameter (i.e. ‘uc’) API is automatically initialized by loading parameters and keys specifically from configuration files of the controller. If you load the eva-apiclient.php library from include/php/ folder, it is not necessary to set dir_eva parameter. Otherwise, it should point to EVA root folder.

EVA_APIClient class should always be initialized manually.

API requires PHP extensions JSON and cURL.

API initialization

API is initialized with the use of the following functions:

  • set_key($key) set API key.
  • set_uri($uri) set the root API URI (i.e., http://localhost:8812). You don’t need to specify the full path to API.
  • set_timeout($timeout) set maximum request timeout (seconds), 5 seconds by default.
  • set_product($product) set controller type: uc for Universal Controller, lm for Logic Manager, sfa for SCADA Final Aggregator. The client automatically identifies which API is to be called - either SYS API one or the one of the certain controller - that is why this parameter is required.
  • ssl_verify($v) to verify or not SSL certificate validity while working through https://. Can be true (check) or false (don’t check). The certificate is verified by default.

Example:

<?php
include "eva-apiclient.php";
$api = new EVA_APIClient();
$api->set_key($APIKEY);
$api->set_uri('http://192.168.0.77:812');
$api->set_product('uc');
?>
API function call

API functions are invoked by calling the call function:

<?php
EVA_APIClient->call($func, $params=null, $timeout=null);
?>

where:

  • $params the dict of the request parameters (if required)
  • $timeout - maximum time (in seconds) to wait for the API response (if not set - the default timeout is used or the one set during API client initialization).

Example:

<?php
include "eva-apiclient.php";
$api = new EVA_APIClientLocal('uc');
list($code, $result) = $api->call('state', array('i' => 'unit1'));
?>

The function returns an array of two variables:

  • 0 API call result
  • 1 the result itself (JSON response converted to Python dict or array).
API result codes

Result codes are stored in module variables:

<?php
# the call succeeded
$result_ok = 0;
# the item is not found or the function requires a different set of
# parameters
$result_not_found = 1;
# access is denied with the set key
$result_forbidden = 2;
# - API error, e.g. the string param was used instead of a number
$result_api_error = 3;
# unknown error: all errors not listed here fall within this category
$result_unknown_error = 4;
# API is not initialized - URI is not set
$result_not_ready = 5;
# Attempt to call API function unknown to the client
$result_func_unknown = 6;
# server connection failed
$result_server_error = 7;
# the server request exceeded the time set in timeout
$result_server_timeout = 8;
# API returned data not in JSON or it cannot be parsed
$result_bad_data = 9;
# action failed (e.g., when calling  SYS API cmd or UC API action functions)
$result_func_failed = 10;
# the function is called with invalid params
$result_invalid_params = 11;
?>

Developing own PHI (Physical Interface) for EVA ICS. HOWTO

PHI (Physical interface) is a low level driver which communicates directly with an equipment. PHI should not contain any logic, its job is only to get/set an equipment to the state requested by LPI.

Required variables in a header

PHI info
  • __author__ module author
  • __copyright__ copyright
  • __license__ module license
  • __version__ module version
  • __description__ module description (keep it short)
PHI system info

the next fields are processed by controller, so make them exactly as required

  • __id__ module ID (usually equals to file name, string)
  • __equipment__ supported equipment (list or string)
  • __api__ module API (integer number)
  • __required__ features required from LPI (Logical to Physical Interface, list):
  • port_get get single port data
  • port_set set single port data
  • aao_get get and process all ports at once
  • aao_set set all ports at once
  • status process item status
  • value process item values
  • action unit actions
  • __mods_required__ required python modules (not included neither in standard Python install nor in EVA ICS)
  • __lpi_default__ if specified, the default driver will be created for this PHI when loaded.
  • __features__ own features provided (list):
  • port_get get single port data
  • port_set set single port data
  • aao_get get all ports at once (if no port is specified)
  • aao_set set all ports at once (if list of ports and list of data is given)
  • universal PHI is universal and process cfg in requests.
  • cache PHI supports state caching (useful for slow devices)
PHI help

Each PHI module should contain 4 help variables:

  • __config__help__ module configuration help (on load)
  • __get_help__ additional configuration params possible for LPI to send with get command
  • __set_help__ additional configuration params possible for LPI to send with set command

First variable should be human readable, others may copy, join or process the first one or each other in any way.

All variables should be in list format, containing dictionaries with the following context:

  • name property name
  • help property description (help)
  • type property type
  • required True if property is required, False if it’s optional

Property type may be:

  • bool boolean (True/False)
  • str string
  • url string containing url
  • int integer
  • uint unsigned integer (greater or equal to 0)
  • hex hexadecimal number
  • bin binary number
  • float float number
  • ufloat unsigned float (greater or equal to 0)
  • list:type list of variables with type specified
  • enum:type:a,b,c list of the permitted specified type values

If the property accepts multiple types, they should be listed via or (|) symbol.

The last one variable is

  • __help__

It should contain the extended PHI description and operation manual. May be in any variable format and use restructured text directives for formatting.

Classes and modules

It’s allowed to import any Python system module or module installed by EVA ICS. If PHI requires installing more modules, they should be listed in PHI help file and in __mods_required__ variable.

Warning

All non-standard modules (not included neither in Python install nor in EVA ICS) should be imported with try/catch with importlib, their unavailability shouldn’t block loading PHI for informational puproses.

Importing modules eva.uc.drivers.tools, eva.tools, eva.traphandler, eva.uc.modbus, eva.uc.smbus and functions from eva.uc.driverapi:

  • get_version() get Driver API version
  • get_polldelay() get EVA poll delay
  • get_timeout() get default timeout
  • critical() send EVA critical call
  • log_traceback() log traceback debug info
  • lock(l, timeout, expires) acquire lock “eva:phi:l”, wait max timeout sec, lock automatically expires in expires sec. Timeout and expiration time can’t be longer than default controller timeout.
  • unlock(l) release lock “eva:phi:l
  • handle_phi_event(phi, port, data) ask Driver API to handle event (see below)

is highly welcome. Importing other EVA modules or driverapi functions is not recommended unless you really know what you do.

The main class is defined as:

from eva.uc.drivers.phi.generic_phi import PHI as GenericPHI

class PHI(GenericPHI):
    #<your code>

Constructor

The constructor should set the above constants to class variables to let them be serialized by parent class if requested:

def __init__(self, phi_cfg=None, info_only=False):
    super().__init__(phi_cfg=phi_cfg, info_only=info_only)
    self.phi_mod_id = __id__
    self.__author = __author__
    self.__license = __license__
    self.__description = __description__
    self.__version = __version__
    self.__api_version = __api__
    self.__equipment = __equipment__
    self.__features = __features__
    self.__required = __required__
    self.__mods_required = __mods_required__
    self.__lpi_default = __lpi_default__
    self.__config_help = __config_help__
    self.__get_help = __get_help__
    self.__set_help = __set_help__
    self.__help = __help__
    if info_only: return
    # your code, i.e. parsing self.phi_cfg

The super().__init__ call should always be first.

If the constructor faces a problem (e.g. parsing a config or checking equipment, e.g. local bus) it may set self.ready=False to abort controller loading the module.

If PHI methods get/set can’t work with single ports at all (e.g. equipment returns state of all ports at once only), constructor should set variables:

  • self.aao_get=True tells LPI the returned with PHI.get method data will always contain all port states even if the port is specified in get.
  • self.aao_set=True asks LPI to collect as much data to set as possible, and then call PHI set method

The parent constructor sets the variable self.phi_cfg to phi_cfg or to {}, so it’s safe to work with it with self.phi_cfg.get(cfgvar).

If info_only param is true, it means the controller loaded module only to get its info and the module doesn’t need to initialize itself to work and perform initial tests.

Primary methods

The following methods should be defined. cfg param may contain configuration params which should override the default ones for the current call.

# if PHI can read data from the equipment
def get(self, port=None, cfg=None, timeout=0):
    #<your code>
    #should return a single state value or a dict { 'port': value }
    #port should always be a string
    #
    #should return None if failed, integer for status, string for values
    #
    #if PHI supports aao_get feature, it should return all port states when
    #no port is specified in request.

# if PHI can write data to the equipment
def set(self, port=None, data=None, cfg=None, timeout=0):
    #<your code>
    #should return True (or result) if passed, False or None if failed
    #
    #If PHI supports aao_set feature, it should deal with a list of ports,
    #if no - with a single port only. If both port_set and aao_set are
    #specified in features, PHI should deal with both single port and list
    #of ports

port and data may be integers, string, contain lists or be set as None. PHI should always be ready to any incoming params and handle the missing or incorrect by itself. If port contains a list, data always contain a list too.

cfg may contain equipment configuration options. If the driver is universal, it should handle them properly.

Warning

watch out for the timeout - if it’s expired, the controller may crash or be forcedly restarted. Always calculate the remaining time for the external calls and return error as soon as it comes closer to expiration.

Method test should perform a self-test (equipment test) if cmd==’self’, other methods are variable and may be used e.g. for debugging. If command is not understood by the method, it’s a rule of good taste to return a help text (dict { ‘command’: ‘command help’ }).

def test(self, cmd=None):
    #<your code>

Method exec may be implemented to perform some actions on the equipment, e.g. changing the equipment settings or manage the firmware. You can implement any commands in any form you wish using cmd and args params.

def exec(self, cmd=None, args=None):
    #<your code>

The method should be used for real commands only, all the tests (e.g. testing get method, obtaining equipment info for testing or informational purposes) should be implemented in test. After the command execution, the method should return OK on success or FAILED on failure. If command is not understood by the method, it’s a rule of good taste to return a help text (dict { ‘command’: ‘command help’ }).

The following methods may be used to call or register/unregister anything on driver load/unload:

def start(self):
    #<your code>

def stop(self):
    #<your code>

Parent methods

Parent class provides the following useful functions:

  • self.set_cached_state(data) set driver cached state (any format)
  • self.get_cached_state() return the state cached before. If the cache is expired (self.cache param handled by parent), the method return None

All the logging should be done with the following methods:

  • self.log_debug(msg)
  • self.log_info(msg)
  • self.log_warning(msg)
  • self.log_error(msg)
  • self.log_critical(msg)
  • self.critical(msg)

The last two methods do the same, logging an event and calling controller critical() method.

Handling events

If the equipment sends any event, PHI should ask Driver API to handle it. This can be done with method

eva.uc.driverapi.handle_phi_event(phi, port, data)

where:

  • phi = self
  • port = port, where the event has happened
  • data = port state values, as much as possible (dict {‘port’: state })

The controller will call update() method for all items using the caller PHI for updating, providing LPIs state data to let them process the event with minimized amount of additional PHI.get() calls.

Value -1 can be used to set unit error status, value False to set sensor error status.

Handling SNMP traps

First you need to subscribe to EVA trap handler. Import eva.traphandler mod and modify PHI start and stop methods:

import eva.traphandler

class PHI(GenericPHI):

    # class code

    def start(self):
        #<your code>
        eva.traphandler.subscribe(self)

    def stop(self):
        #<your code>
        eva.traphandler.unsubscribe(self)

EVA trap handler calls method process_snmp_trap(data) for each object subscribed, so let’s create it inside a primary class:

def process_snmp_trap(self, host, data):
    #<your code>

host IP address of the host where SNMP trap is coming from.

data a dict with name/value pairs, where name is SNMP numeric OID without a first dot, and value is always a string. Check if this trap belongs to your device and perform the required actions. Don’t worry about the timeout (except for the actual reaction time on a trap event) because every method is being executed in its own thread.

EVA traphandler doesn’t care about the method return value and you must process all the errors by yourself.

Schedule events

If the equipment doesn’t send any events, PHI can initiate updating the items by itself. To perform this, PHI should support aao_get feature and be loaded with update=N config param. Updates, intervals as well as the whole update process are handled by parent class.

Working with I2C/SMBus

It’s highly recommended to use internal UC locking for I2C bus. Then you can use any module available to work with I2C/SMBus. As there are a lot of modules with similar functions, you can choose it on your own. See the code example below:

# ...........
# we'll use smbus2 module in this example
__mods_required__ = ['smbus2']
# ...........
# import i2c locker module
import eva.uc.i2cbus

def __init__(self, phi_cfg=None, info_only=False):
    # code
    try:
        self.smbus2 = importlib.import_module('smbus2')
    except:
        self.log_error('unable to load smbus2 python module')
        self.ready = False
        return

def get(self, port=None, cfg=None, timeout=0):
    if not eva.uc.i2cbus.lock(self.bus):
        self.log_error('unable to lock I2C bus')
        return None
    bus = self.smbus2.SMBus(self.bus)
    # perform some operations, then release the bus for other threads
    eva.i2cbus.release(self.bus)
    return result

All I2C/SMBus exceptions, timeouts and retries should be handled by the code of your PHI.

Working with ModBus

Working with ModBus is pretty easy. PHIs don’t need to care about the ModBus connection and data exchange at all, everything is managed by eva.uc.modbus module.

# everything you need is just import module
import eva.uc.modbus as modbus

def __init__(self, phi_cfg=None, info_only=False):
    # ....
    # it's recommended to force aao_get in ModBus PHI to let it read states
    # with one modbus request
    self.aao_get = True
    self.modbus_port = self.phi_cfg.get('port')
    # check in constructor if the specified modbus port is defined
    if not modbus.is_port(self.modbus_port):
        self.log_error('modbus port ID not specified or invalid')
        self.ready = False
        return
    # store unit id PHI is loaded for
    try:
        self.unit_id = int(self.phi_cfg.get('unit'))
    except:
        self.ready = False
        return

def get(self, port=None, cfg=None, timeout=0):
    # modbus.get_port(port_id) function returns:
    # False - if port failed to connect,
    # None - if port doesn't exist or may exceed the timeout,
    # 0 - if port is locked and busy,
    # or the port object itself
    mb = modbus.get_port(self.modbus_port, timeout)
    if not mb: return None
    # The port object is a regular pymodbus object
    # (https://pymodbus.readthedocs.io) and supports all pymodbus functions.
    # All the functions are wrapped with EVA modbus module which handles
    # all errors and retry attempts. The ports PHI gets are always in the
    # connected state.
    r = mb.read_coils(0, 16, unit=self.unit_id)
    # Release modbus port as soon as possible to let other components work
    # with it while your PHI is processing the data
    mb.release()
    # result is a regular pymodbus result
    if rr.isError(): return None
    # let's convert 16 coils to 16 port states
    result = {}
    try:
        for i in range(16):
            result[str(i + 1)] = 1 if rr.bits[i] else 0
    except:
        result = None
    return result

The variable client_type of the port object (mb.client_type) holds the port type (tcp, udp, rtu, ascii or binary). This can be used to make PHI work with the equipment of the same type which uses e.g. different registers for different connection types.

Working with MQTT

The best way to work with MQTT is to use EVA ICS notification system connections. Instead of creating own MQTT connection and manage topics, let EVA core do its job. If your equipment and EVA ICS use different MQTT servers, just create new MQTT notifier to equipment server in EVA ICS without any subscriptions.

Note

If space is specified in EVA MQTT notifier, all topics should be relative, e.g. if space=test, MQTT can send and subscribe only to topics below the space level: equipment1/POWER will send/subscribe to test/equipment1/POWER.

Use eva.uc.drivers.tools.mqtt.MQTT class to deal with notifiers. If no notifier_id is specified eva_1 notifier is used.

Warning

MQTT custom handlers may be started in different threads. Don’t forget to use locking mechanisms if required.

Let’s deal with an equipment which has MQTT topic topic/POWER with values ON/OFF:

# everything you need is just import class
from eva.uc.drivers.tools.mqtt import MQTT
# and a function to handle events
from eva.uc.driverapi import handle_phi_event

def __init__(self, phi_cfg=None, info_only=False):
# ....
self.topic = self.phi_cfg.get('t')
self.mqtt = MQTT(self.phi_cfg.get('n'))
self.current_status = { '1': None }
if self.topic is None or self.mqtt is None:
    self.ready = False

def get(self, port=None, cfg=None, timeout=0):
    # as we can not query equipment, return saved status instead
    return self.current_status


def set(self, port=None, data=None, cfg=None, timeout=0):
    # .... check data, prepare
    try:
        state = int(data)
    except:
        return False
    # then use MQTT.send function to send data to desired topic
    self.mqtt.send(self.topic + '/POWER', 'ON' if state else 'OFF')
    return True

def start(self):
    # register a custom handler for MQTT topic
    self.mqtt.register(self.topic + '/POWER', self.mqtt_handler)

def stop(self):
    # don't forget to unregister a custom handler when PHI is unloaded
    self.mqtt.unregister(self.topic + '/POWER', self.mqtt_handler)

def mqtt_state_handler(self, data, topic, qos, retain):
    # update current status
    self.current_status['1'] = 1 if data == 'ON' else 0
    # then handle PHI event
    handle_phi_event(self, 1, self.get())

Working with UDP API

You may use EVA UDP API to receive custom UDP packets and then parse them in PHI. This allows to create various hardware bridges e.g. from 315/433/866 MHz radio protocols, obtaining radio packets with custom programmed hardware appliance and then send them to EVA ICS to handle.

Custom packet format is (\x = hex):

\x01 HANDLER_ID \x01 DATA

DATA is always transmitted to handler in binary format. UDP API encryption, authentication and batch commands in custom packets are not supported (unless managed by handler).

Warning

UDP API custom handlers may be started in different threads. Don’t forget to use locking mechanisms if required.

import eva.udpapi as udp

def __init__(self, phi_cfg=None, info_only=False):
# ....

def start(self):
    # subscribe to UDP API using PHI ID as handler ID
    udp.subscribe(__id__, self.udp_handler)

def stop(self):
    # don't forget to unsubscribe when PHI is unloaded
    udp.unsubscribe(__id__, self.udp_handler)

def udp_handler(self, data, address):
    _data = data.decode()
    self.log_debug('got data: {} from {}'.format(_data, address))
    # process the data
    # ...

Exceptions

The methods of PHI should not raise any exceptions and handle/log all errors by themselves.

Testing

Use bin/test-phi command-line tool to perform PHI module tests. The tool requires test scenario file, which may contain the following functions:

  • debug() turn on debug mode (verbose output), equal to -D command-line option
  • nodebug() turn off debug mode
  • modbus(params) create virtual ModBus port with ID default
  • load(phi_mod, phi_cfg=None) load PHI module for tests. PHI cfg may be specified either as string or as dictionary
  • get(port=None, cfg=None, timeout=None) call PHI get function
  • set(port=None, data=None, cfg=None, timeout=None) call PHI set function
  • test(cmd=None) call PHI test function
  • exec(cmd=None, args=None) call PHI exec function
  • sleep(seconds) delay execution for a given number of seconds (alias for time.sleep)

additionally, each function automatically prints the result. Test scenario is actually a Python code and may contain any Python logic, additional module imports etc.

Example test scenario. Let’s test dae_ro16_modbus module:

debug()
modbus('tcp:192.168.55.11:502')
load('dae_ro16_modbus', 'port=default,unit=1')
if test('self') != 'OK': exit(1)
set(port=2,data=1)
set(port=5,data=1)
get()
set(port=2,data=0)

Macro extensions

As macros are written in Python, you can use any Python module to extend your macros. Additionally Logic Manager has ability to extend macros with extensions.

Modules are more flexible and are a standard Python way to extend your software. However macro extensions are more simple, standardized and easy to configure. As the goal is to keep macro code as simple as possible, macro extensions are the best choice in many cases.

Loading macro extension

Macro extensions are stored in xc/extensions folder. To list available macro extension modules, use command:

lm-cmd ext mods

Next command returns extension info:

lm-cmd ext modinfo <ext_module>

To get information about extension configuration and functions provided, use commands:

lm-cmd ext modhelp <ext_module> cfg
lm-cmd ext modhelp <ext_module> functions

To load/unload macro extension, use command:

# load
lm-cmd ext load [-c CONFIG] [-y] <ext_id> <ext_module>
# unload
lm-cmd ext unload <ext_id>

where:

  • -c CONFIG extension configuration options, comma separated
  • -y save extension config after successful load

Extension functions

When extension is loaded, its functions become available in all macros automatically with names <ext_id>_<function>.

E.g. when extension audio is loaded with ID a1, its function play is available as a1_play. This allows you to load one extension multiple times and have different functionality according to specified configuration without need to configure module/class params in macros.

If you want to make a short alias for extension function, use alias (e.g. in xc/lm/common.py):

alias('play', 'a1_play')

Unlike play=a1_play alias doesn’t throw an exception and let macros work even if extension is failed to load or its functions are not available.

Included extensions

The following extensions are included in EVA ICS distribution by default:

Developing your own extension

Create new Python file in xc/extensions folder.

Required variables in a header
  • __author__ module author
  • __copyright__ copyright
  • __license__ module license
  • __version__ module version
  • __description__ module description (keep it short)
  • __id__ module ID (usually equals to file name, string)
  • __api__ module API (integer number)
  • __mods_required__ required python modules (included neither in standard Python install nor in EVA ICS)
  • __config__help__ module configuration help (on load)
  • __functions__ exported functions
  • __help__ should contain the extended description and operation manual. May be in any variable format and use restructured text directives for formatting.
Configuration variable

Configuration variable (__config_help__) should be in list format, containing dictionaries with the following context:

  • name property name
  • help property description (help)
  • type property type
  • required True if property is required, False if it’s optional

Property type may be:

  • bool boolean (True/False)
  • str string
  • url string containing url
  • int integer
  • uint unsigned integer (greater or equal to 0)
  • hex hexadecimal number
  • bin binary number
  • float float number
  • ufloat unsigned float (greater or equal to 0)
  • list:type list of variables with type specified
  • enum:type:a,b,c list of permitted specified type values

If a property accepts multiple types, they should be listed via or (|) symbol.

Exported functions

Exported functions (__functions__) variable is a dictionary in format:

{ 'function(params)': 'description' }
# e.g.
{
    'func1(param1, param2=0, param3=True)': 'This function does something',
    'func2(param1=0)': 'This function does something else'
}

All exported functions should be defined in a primary extension class.

Classes and modules

It’s allowed to import any Python system module or module installed by EVA ICS. If extension requires installing more modules, they should be listed in extension help and in __mods_required__ variable.

Warning

All non-standard modules (not included neither in Python install nor in EVA ICS) should be imported with try/catch with importlib, their unavailability shouldn’t block loading extension for informational purposes.

Importing EVA modules and functions from eva.lm.extapi:

  • get_version() get Extension API version
  • get_polldelay() get EVA poll delay
  • get_timeout() get default timeout
  • critical() send EVA critical call
  • log_traceback() log traceback debug info

is highly welcome.

The main class is defined as:

from eva.lm.extensions.generic import LMExt as GenericExt

class LMExt(GenericExt):
    #<your code>
Constructor

The constructor should set the above constants to class variables to let them be serialized by parent class if requested:

def __init__(self, cfg=None, info_only=False):
    super().__init__(cfg)
    self.mod_id = __id__
    self.__author = __author__
    self.__license = __license__
    self.__description = __description__
    self.__version = __version__
    self.__mods_required = __mods_required__
    self.__api_version = __api__
    self.__config_help = __config_help__
    self.__functions = __functions__
    self.__help = __help__
    if info_only: return
    # your code, i.e. parsing self.cfg

The super().__init__ call should always be first.

If the constructor faces a problem (i.e. parsing a config or checking required modules) it may set self.ready=False to abort controller loading the extension.

Exceptions

There’s no standard way to handle exceptions, however if any of exported functions raise them, this should be specified in extension help and readme file.

Testing

Use bin/test-ext command-line tool to perform PHI module tests. The tool requires test Python file, which loads extension as _ and contains all its functions (e.g. __test for extension.test):

print('Testing extension')
__test(params)
__func2(params)
__func3(params)
print('Test completed')