Core plug-ins

Developing core plug-ins is the most advanced way to extend EVA ICS functionality. You can create own API methods, functions for Logic control macros and SFA Templates plus much and much more.

Guide by example

Note

All plugin objects and custom API functions are always registered as x_{plugin}_{name}

"""
EVA ICS plugin example

To load the plugin, put its config to the registry key,
config/<controller>/plugins/<plugin_name>

...........................................
enabled: true
config: {}
...........................................

Plugins can be one-file, in this case they can be just put into plugins
(/opt/eva/runtime/plugins) directory and named e.g. "my.py".

If your plugin is more complex or you want to redistribute it e.g. via PyPi -
create Python module called "evacontrib.<yourpluginname>" and install it either
globally or in EVA ICS venv.

Plugin configuration:

use either "eva <controller> edit plugin-config <plugin-name>" command or
edit the registry key eva3/HOST/config/<controller>/plugins/<plugin-name>

......
enabled: true
config:
  var1: value1
  var2: value2
......
"""

# plugin header, required
__author__ = 'Altertech, https://www.altertech.com/'
__copyright__ = 'Copyright (C) 2012-2021 Altertech'
__license__ = 'Apache License 2.0'
__version__ = "3.4.2"

# import EVA ICS Plugin API library
import eva.pluginapi as pa
"""
"flags" namespace can be defined, EVA ICS uses this namespace to determine
plugin status:

flags.ready = True # means plugin is correctly initialized and works properly
"""
from types import SimpleNamespace
flags = SimpleNamespace(ready=False)

# init plugin logger
logger = pa.get_logger()


def init(config, **kwargs):
    """
    Called by EVA ICS core when the initial configuration is loaded. All
    methods are optional, if the method doesn't exist in plugin, no exception
    is raised

    All methods should have **kwargs in argument list to accept extra arguments
    from future Plugin API versions

    Args:
        config: plugin configuration (comes as key/value dict)
    """
    # require Plugin API version 1+
    pa.check_version(1)
    # this feature is only for LM PLC
    try:
        pa.check_product('lm')
        # register new lmacro function "mytest"
        pa.register_lmacro_object('mytest', mytest)
        # register lmacro object "weekend"
        pa.register_lmacro_object('weekend', weekend)
    except:
        pass
    # this feature is only for SFA
    try:
        pa.check_product('sfa')
        # register SFA Templates function "weekend_days"
        pa.register_sfatpl_object('weekend_days', get_weekend)
    except:
        pass
    """
    register API extension blueprint

    currently only JSON RPC and direct API calling methods can be registered
    """
    pa.register_apix(MyAPIFuncs(), sys_api=False)
    flags.ready = True


def before_start(**kwargs):
    """
    Called right before controller start
    """
    logger.info('plugin my before start called')


def start(**kwargs):
    """
    Called after controller start
    """
    logger.info('plugin my start called')


def before_stop(**kwargs):
    """
    Called right before controller stop
    """
    logger.info('plugin my before stop called')


def stop(**kwargs):
    """
    Called after controller stop
    """
    logger.info('plugin my stop called')


def dump(**kwargs):
    """
    Called after controller stop
    """
    return 'something'


def handle_state_event(source, data, **kwargs):
    """
    Called when any state event is received

    Args:
        source: event source item (object)
        data: serialized item state dict
    """
    logger.info(f'event from {source.oid}')
    logger.info(data)


def handle_api_call(method, params, **kwargs):
    """
    Called before API methods

    If returned False, API raises FunctionFailed exception
    If any standard PluginAPI exception is raised, API returns the correspoding
    error

    Args:
        method: method name
        params: method params
    """
    logger.info(f'API METHOD CALLED: {method} with params {params}')
    if method == 'destroy':
        raise pa.AccessDenied('Method is disabled')


def handle_api_call_result(method, params, result, **kwargs):
    """
    Called after API methods

    If returned False, API raises FunctionFailed exception
    If any standard PluginAPI exception is raised, API returns the correspoding
    error

    Args:
        method: method name
        params: method params
        result: method result
    """
    logger.info(f'API METHOD: {method} with params {params}, RESULT: {result}')


# custom plugin code

weekend = ['Sat', 'Sun']


# the function we registered for SFA TPL
def get_weekend():
    return ','.join(weekend)


# the function we registered for LM PLC macros
def mytest():
    logger.info('something')


# APIX blueprint to implement new API functions
class MyAPIFuncs(pa.APIX):

    # log API call as DEBUG
    @pa.api_log_d
    # require master key
    @pa.api_need_master
    def my_square(self, **kwargs):
        # parse incoming params
        x = pa.parse_api_params(kwargs, 'x', 'N')
        if x < 0:
            raise pa.InvalidParameter('x < 0')
        # return some result
        # if API method produces no result, it SHOULD return True
        return {
            'result': x * x,
            'you': pa.get_aci('key_id'),
            'me': [pa.get_directory('eva'),
                   pa.get_product().build]
        }

    # log API call as INFO
    @pa.api_log_i
    # require master key
    @pa.api_need_master
    def my_test(self, **kwargs):
        # let's return the result of test_phi API function
        return pa.api_call('test_phi', i='ct1', c='self')

Resources

class eva.pluginapi.APIX

API blueprint extension class

exception eva.pluginapi.AccessDenied(msg='', kb=None)

raised when call has no access to the resource

__str__()

Return str(self).

exception eva.pluginapi.FunctionFailed(msg='', kb=None)

raised with function failed with any reason

__str__()

Return str(self).

exception eva.pluginapi.InvalidParameter
__str__()

Return str(self).

__weakref__

list of weak references to the object (if defined)

class eva.pluginapi.MQTT(notifier_id)

MQTT helper class

Parameters

notifier_id – MQTT notifier to use (default: eva_1)

__init__(notifier_id)
Parameters

notifier_id – MQTT notifier to use (default: eva_1)

__weakref__

list of weak references to the object (if defined)

register(topic, func, qos=1)

Register MQTT topic handler

send(topic, data, retain=None, qos=1)

Send MQTT message

unregister(topic, func)

Unregister MQTT topic handler

exception eva.pluginapi.MethodNotFound

raised when requested method is not found

__str__()

Return str(self).

__weakref__

list of weak references to the object (if defined)

exception eva.pluginapi.MethodNotImplemented(msg='', kb=None)

raised when requested method exists but requested functionality is not implemented

__str__()

Return str(self).

exception eva.pluginapi.ResourceAlreadyExists(msg='', kb=None)

raised when requested resource already exists

__str__()

Return str(self).

exception eva.pluginapi.ResourceBusy(msg='', kb=None)

raised when requested resource is busy (e.g. can’t be changed)

__str__()

Return str(self).

exception eva.pluginapi.ResourceNotFound(msg='', kb=None)

raised when requested resource is not found

__str__()

Return str(self).

exception eva.pluginapi.TimeoutException(msg='', kb=None)

raised when call is timed out

eva.pluginapi.api_call(method, key_id=None, **kwargs)

Call controller API method

Parameters
  • key_id – API key ID. If key_id is None, masterkey is used

  • other – passed to API method as-is

Returns

API function result

Raises

eva.exceptions

eva.pluginapi.api_log_d(f)

API method decorator to log API call as DEBUG

eva.pluginapi.api_log_i(f)

API method decorator to log API call as INFO

eva.pluginapi.api_log_w(f)

API method decorator to log API call as WARNING

eva.pluginapi.api_need_cmd(f)

API method decorator to pass if API key has “cmd” allowed

eva.pluginapi.api_need_file_management(f)

API method decorator to pass if file management is allowed in server config

eva.pluginapi.api_need_lock(f)

API method decorator to pass if API key has “lock” allowed

eva.pluginapi.api_need_master(f)

API method decorator to pass if API key is masterkey

eva.pluginapi.api_need_rpvt(f)

API method decorator to pass if rpvt is allowed in server config

eva.pluginapi.api_need_sysfunc(f)

API method decorator to pass if API key has “sysfunc” allowed

eva.pluginapi.check_product(code)

Check controller type

Parameters

code – required controller type (uc, lm or sfa)

Raises

RuntimeError – if current controller type is wrong

eva.pluginapi.check_version(min_version)

Check plugin API version

Parameters

min_version – min Plugin API version required

Raises

RuntimeError – if Plugin API version is too old

eva.pluginapi.clear_thread_local(var, mod=None)

Check if thread-local variable exists

Parameters
  • var – variable name

  • mod – self module name (optional)

Returns

True if exists

eva.pluginapi.create_db_engine(db_uri, timeout=None)

Create SQLAlchemy database Engine

  • database timeout is set to core timeout, if not specified

  • database pool size is auto-configured

  • for all engines, except SQLite, “READ UNCOMMITED” isolation level is used

eva.pluginapi.critical()

Send critical event

eva.pluginapi.format_db_uri(db_uri)

Formats short database URL to SQLAlchemy URI

  • if no DB engine specified, SQLite is used

  • if relative SQLite db path is used, it’s created under EVA dir

eva.pluginapi.get_aci(field, default=None)

get API call info field

Parameters
  • field – ACI field

  • default – default value if ACI field isn’t set

Returns

None if ACI field isn’t set

eva.pluginapi.get_db()

get SQLAlchemy connection to primary DB

eva.pluginapi.get_directory(tp)

Get path to EVA ICS directory

Parameters

tp – directory type: eva, runtime, ui, pvt or xc

Raises

LookupError – if directory type is invalid

eva.pluginapi.get_item(i)

Get controller item

Parameters

i – item oid

Returns

None if item is not found

eva.pluginapi.get_logger(mod=None)

Get plugin logger

Parameters

mod – self module name (optional)

Returns

logger object

eva.pluginapi.get_masterkey()

get master API key

Returns

master API key

eva.pluginapi.get_plugin_db(db, mod=None)

Get plugin custom database SQLAlchemy connection

The connection object is stored as thread-local and re-used if possible

Parameters

db – SQLAlchemy DB engine

eva.pluginapi.get_polldelay()

Get core poll delay

eva.pluginapi.get_product()

Get product object

Returns

namespace(name, code, build)

eva.pluginapi.get_sleep_step()

Get core sleep step

eva.pluginapi.get_system_name()

Get system name (host name)

eva.pluginapi.get_thread_local(var, default=None, mod=None)

Get thread-local variable

Parameters
  • var – variable name

  • default – default, if doesn’t exists

  • mod – self module name (optional)

Returns

variable value or None if variable isn’t set

eva.pluginapi.get_timeout()

Get default timeout

eva.pluginapi.get_userdb()

get SQLAlchemy connection to user DB

eva.pluginapi.get_version()

Get Plugin API version

eva.pluginapi.has_thread_local(var, mod=None)

Check if thread-local variable exists

Parameters
  • var – variable name

  • mod – self module name (optional)

Returns

True if exists

eva.pluginapi.key_by_id(key_id)

get API key by API key ID

Returns

API key

eva.pluginapi.key_check(*args, ro_op=False, **kwargs)

check API key access

Arguments are ACL which can be combined

Parameters
  • k – API key, required

  • items – item objects

  • oid – OID (mqtt-style masks allowed)

  • allow – check allows

  • pvt_file – access to pvt resource

  • pvt_file – access to rpvt resource

  • ip – caller IP

  • master – is master access required

  • sysfunc – is sysfunc required

  • ro_op – is item operation read-only

eva.pluginapi.key_check_master(*args, ro_op=False, **kwargs)

check master API key access

Parameters
  • k – API key, required

  • ro_op – is item operation read-only

eva.pluginapi.key_id(k)

get key ID by API key

Returns

API key ID

eva.pluginapi.log_traceback()

Log traceback

eva.pluginapi.parse_api_params(params, names='', types='', defaults=None)

calls parse_function_params but omits API key

eva.pluginapi.parse_function_params(params, names, types='', defaults=None, e=<class 'eva.tools.InvalidParameter'>, ignore_extra=False)
Parameters
  • names – parameter names (list or string if short) S: equal to ‘save’ Y: equal to ‘full’ J: equal to ‘_j’ F: equal to ‘force’

  • values – parameter values R: required, any not null and non-empty string r: required, but empty strings are possible s: required, should be string S: required, should be non-empty string b: boolean (or 0/1 or boolean-like strings) B: boolean (or 0/1 or boolean-like strings), required i: integer, can be None f or n: float(number), can be None I: integer, required F or N: float(number), required D: dict, required T: tuple, required X: set, required L: list, required . (dot): optional o: oid, can be null O: OID required

  • params – dict

  • defaults – dict (name/value)

  • e – exception to raise

eva.pluginapi.register_apix(o, sys_api=False, mod=None)

Register API extension (APIX) object

All object methods (except internal and private) are automatically exposed as API functions

Functions are registered as x_{plugin}_{fn}

Parameters
  • o – APIX object

  • sys_api – if True, object functions are registered as SYS API

  • mod – self module name (optional)

eva.pluginapi.register_lmacro_object(n, o, mod=None)

Register custom object for LM PLC macros

Object is registered as x_{plugin}_{n}

Parameters
  • n – object name

  • o – object itself

  • mod – self module name (optional)

eva.pluginapi.register_sfatpl_object(n, o, mod=None)

Register custom object for SFA Templates

Object is registered as x_{plugin}_{n}

Parameters
  • n – object name

  • o – object itself

  • mod – self module name (optional)

eva.pluginapi.sendmail(subject=None, text=None, rcp=None)

send email message

The function uses config/common/mailer EVA ICS registry key get sender address and list of the recipients (if not specified).

Optional:

subject: email subject text: email text rcp: recipient or array of the recipients

Raises

FunctionFailed – mail is not sent

eva.pluginapi.set_aci(field, value)

set API call info field

Parameters
  • field – ACI field

  • value – field value

Returns

True if value is set, False for error (e.g. ACI isn’t initialized)

eva.pluginapi.set_thread_local(var, value=None, mod=None)

Set thread-local variable

Parameters
  • var – variable name

  • value – value to set

  • mod – self module name (optional)

eva.pluginapi.snmp_get(oid, host, port=161, community='public', timeout=0, retries=0, rf=<class 'str'>, snmp_ver=2, walk=False)
Parameters
  • oid – SNMP OID or MIB name

  • host – target host

  • port – target port (default: 161)

  • community – SNMP community (default: public)

  • timeout – max SNMP timeout

  • retries – max retry count (default: 0)

  • rf – return format: str, float, int or None

  • snmp_ver – SNMP version (default: 2)

  • walk – if True, SNMP walk will be performed

Returns

If rf is set to None, raw pysnmp object is returned, otherwise parsed to float, int or str

If walk is requested, list of pysnmp objects is returned

eva.pluginapi.snmp_set(oid, value, host, port=161, community='private', timeout=0, retries=0, snmp_ver=2)
Parameters
  • oid – SNMP OID or MIB name

  • value – value to set

  • host – target host

  • port – target port (default: 161)

  • community – SNMP community (default: public)

  • timeout – max SNMP timeout

  • retries – max retry count (default: 0)

  • snmp_ver – SNMP version (default: 2)

Returns

True if value is set, False if not

eva.pluginapi.spawn(f, *args, **kwargs)

Run function as a thread in EVA ICS thread pool

Parameters
  • f – callable

  • args/kwargs – passed to function as-is

Returns

concurrent.futures Future object

eva.pluginapi.upnp_discover(st, ip='239.255.255.250', port=1900, mx=True, interface=None, trailing_crlf=True, parse_data=True, discard_headers=['Cache-control', 'Host'], timeout=None)

discover uPnP equipment

Parameters
  • st – service type

  • ip – multicast ip

  • port – multicast port

  • mx – use MX header (=timeout)

  • interface – network interface (None - scan all)

  • trailing_crlf – put trailing CRLF at the end of msg

  • parse_data – if False, raw data will be returned

  • discard_headers – headers to discard (if parse_data is True)

  • timeout – socket timeout (for a single interface)

Returns

list of dicts, where IP=equipment IP, otherwise dict, where key=equipment IP addr, value=raw ssdp reply. Note: if data is parsed, all variables are converted to lowercase and capitalized.

Return type

if data is parsed