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