Python Logic Macros
Requires: Python macros controller.
A Python macro is a file, located by default in the folder xc/py/ (the symlink to runtime/xc/py) under the name <macro_id>.py, e.g. test.py for lmacro:my_macros/test. Macro id must be unique within the single EVA ICS node, OID - 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, set action executor service for it:
action:
svc: eva.controller.py
enabled: true
oid: lmacro:test/m1
After, a macro can be started using eva-shell action run command, eva.core::run bus method, by another service, or by request from a remote node.
Common principles of macros operation
Macros are launched simultaneously: the controller does not wait for the completion of the macro and launches its next copy or another macro in parallel. If the only one copy of macro is allowed to operate at the certain point of time or to block execution of other macros, use macro lock and unlock functions.
The controller architecture does not provide the possibility to stop macro from outside, that is why macros should have minimum internal logic and cycles. These ones should be implemented by another services.
As EVA ICS allows to run multiple Python macro controller instances, take in account that the default shared variables are not acceptable within different controllers, use lvars or 3rd party service instead.
Warning
In EVA ICS v4 all Python macro functions require OIDs only. Mean, e.g. reset(“my/lvar”) is no longer accepted, use reset(“lvar:my/var”) instead.
System macros
On startup
If exists, a macro from the autoexec.py file is launched automatically at the controller startup. This macro is not always the first one executed, as far as some service may call other macros before.
Example of autoexec macro usage:
# both cycle timers are expired
if is_expired('lvar:timers/timer1') and is_expired('lvar:timers/timer2'):
# launch the first cycle process
action('lvar:pumps/pump1', on)
# start the first cycle timer
reset('lvar:timers/timer1')
On shutdown
If exists, a macro from the shutdown.py file is launched automatically at the controller shutdown. This macro can, for example, gracefully stop some processes and set/reset required lvars.
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 ACLs, the following must be taken into account: if a macro runs other macros using run function or executes a unit action/sets lvars etc., these tasks can be executed even if the ACL 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 EVA ICS functions.
Built-in exceptions
The following additional exceptions are available:
FunctionFailed
ResourceAlreadyExists (alias: ResourceBusy)
ResourceNotFound
AccessDenied
InvalidParameter
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
_polldelay controller poll delay
_timeout controller default timeout
args array list of arguments the macro is being executed with
kwargs dict of keyword 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
out macro may use this variable to output the data which will be set to out field of the execution result
is_shutdown contains a function which returns True if macro caller got a core shutdown
aci/acl if called by a user, contains the user’s API call info and ACL
Built-in functions
General functions
bus_publish - publishes a message to a bus topic
bus_publish('SOME/TOPIC', 'Hello, World!')
Parameters:
topic topic name
payload message payload
qos QoS level (default: 0)
date - date/time
r = date()
Returns:
Serialized date/time object (dict)
{
"day": 14,
"hour": 0,
"minute": 47,
"month": 5,
"second": 16,
"timestamp": 1557787636.680612,
"weekday": 1,
"year": 2019
}
exit - finishes macro execution
exit(1)
Parameters:
code macro exit code (default: 0, no errors)
get_directory - gets path to EVA ICS directory
Parameters:
tp directory type: eva, runtime, svc_data, venv or xc
Raises:
LookupError if the directory type is invalid
instant - system monotonic timer value
Alias for time.perf_counter
r = instant()
print(r)
522.5493
ls - lists files in directory
If recursive is true, the pattern “**” will match any files and zero or more directories and subdirectories.
r = ls('/opt/i/*.jpg')
Parameters:
mask path and mask (e.g. /opt/data/*.jpg)
recursive if True, perform a recursive search
Returns:
dict with fields ‘name’ ‘file’, ‘size’ and ‘time’ { ‘c’: created, ‘m’: modified }
[
{
"file": "/opt/i/20170926_004347.jpg",
"name": "20170926_004347.jpg",
"size": 6464873,
"time": {
"c": 1553460493.280853,
"m": 1506379536.0
}
},
{
"file": "/opt/i/20171017_095941.jpg",
"name": "20171017_095941.jpg",
"size": 1650389,
"time": {
"c": 1553460493.2968528,
"m": 1510695841.0
}
},
{
"file": "/opt/i/20171029_194029.jpg",
"name": "20171029_194029.jpg",
"size": 3440296,
"time": {
"c": 1553460493.324853,
"m": 1510695762.0
}
},
{
"file": "/opt/i/20170926_004334.jpg",
"name": "20170926_004334.jpg",
"size": 6523001,
"time": {
"c": 1553460493.1648533,
"m": 1506379526.0
}
}
]
mail - sends email message
Requires mailer svc to be set in the controller config
mail(subject='we have a problem', text='sensor 5 is down')
Optionally:
subject email subject
text email text
rcp email recipient or array of the recipients
i user login recipient or array of the recipients
Raises:
FunctionFailed mail is not sent
ping - pings a remote host
Requires fping tool
Parameters:
host host name or IP to ping
timeout ping timeout in milliseconds (default: 1000)
count number of packets to send (default: 1)
Returns:
True if host is alive, False if not
rpc_call - performs a bus RPC call
The method parameters are specified in kwargs
r = rpc_call('item.state', i='unit:tests/door')
Parameters:
method method
Optionally:
_target target service (default: eva.core)
_timeout call timeout
Returns:
the bus call result
{
"act": 0,
"connected": true,
"ieid": [
2225,
24731566246084
],
"node": "mws1",
"oid": "unit:tests/door",
"status": 1,
"t": 1651971627.58268,
"value": null
}
run - executes another lmacro
Args and kwargs are passed to the target lmacro as-is, except listed below.
r = run('lmacro:tests/test1', v1='test', v2=999, _wait=2)
Parameters:
_oid lmacro OID
Optionally:
_wait wait for the completion for the specified number of seconds
_priority queue priority (default is 100, lower is better)
Returns:
Serialized macro action object (dict)
{
"err": null,
"exitcode": 0,
"finished": true,
"node": "mws1",
"oid": "lmacro:tests/test1",
"out": "all is fine",
"params": {
"kwargs": {
"v1": "test",
"v2": 999
}
},
"priority": 100,
"status": "completed",
"svc": "eva.controller.py",
"time": {
"accepted": 1651971891.772146,
"completed": 1651971891.772503,
"created": 1651971891.7694325,
"pending": 1651971891.7723224,
"running": 1651971891.7723744
},
"uuid": "3c291d89-9f25-4a2c-ad88-699867a8ce6b"
}
Raises:
ResourceNotFound macro not found
service - get the service object for the direct access
e.g. service.bus: direct access to BUS/RT, service.rpc: direct access to BUS/RT RPC
Returns:
the service object
set_alarm - sets alarm state
Requires alarm svc to be set in the controller config
set_alarm('lvar:alarm/default/mws1/20/test/AL001', 'TL')
Parameters:
oid alarm OID
op alarm operation
Optionally:
source alarm source (default: lmacro)
Raises:
FunctionFailed failed to set alarm state
sha256sum - calculates SHA256 sum
Parameters:
value value to calculate
hexdigest return binary digest or hex (True, default)
Returns:
sha256 digest
sleep - sleep(seconds)
Delay execution for a given number of seconds. The argument may be a floating point number for subsecond precision.
sleep(0.1)
system - execute the command in a subshell
Alias for os.system
r = system('touch /tmp/1.dat')
print(r)
0
Returns:
shell exit code (0 - no error)
system_name - get the system name
r = system_name()
print(r)
myhost
Returns:
system name
time - current time in seconds since Epoch
Return the current time in seconds since the Epoch. Fractions of a second may be present if the system clock provides them.
r = time()
print(r)
1553461581.549374
Item functions
state - gets item state
r = state('unit:tests/door')
Parameters:
oid item OID or mask
Returns:
item status/value dict or list for mask
{
"act": 0,
"connected": true,
"ieid": [
2225,
24731566246084
],
"node": "mws1",
"oid": "unit:tests/door",
"status": 1,
"t": 1651971627.58268,
"value": null
}
Raises:
ResourceNotFound item not found
status - gets item status
r = status('unit:tests/unit1')
print(r)
0
Parameters:
oid item OID
Returns:
item status (integer)
Raises:
ResourceNotFound item not found
update_state - updates item state
update_state('sensor:tests/temp', dict(value=20))
update_state('sensor:tests/failed', dict(status=-1))
Parameters:
oid item OID
state new state (may contain status/value/t and other state fields)
value - gets item value
r = value('sensor:env/temp_test')
print(r)
191.0
Parameters:
i item OID
Optionally:
default value if null (default is empty string)
Returns:
item value
Raises:
ResourceNotFound item not found
LVar functions
clear - clears lvar status
Set lvar status to 0 or stop timer lvar (set timer status to 0)
clear('lvar:tests/test1')
Parameters:
oid lvar OID
Raises:
FunctionFailed lvar value set error
ResourceNotFound lvar not found
decrement - decrements lvar value
decrement('lvar:tests/test1')
Parameters:
oid lvar OID
Raises:
FunctionFailed lvar value decrement error
ResourceNotFound lvar not found
increment - increments lvar value
increment('lvar:tests/test1')
Parameters:
lvar_id lvar OID
Raises:
FunctionFailed lvar value increment error
ResourceNotFound lvar not found
is_expired - checks is lvar (timer) or item state expired/error
r = is_expired('lvar:nogroup/timer1')
print(r)
True
Parameters:
oid item OID
Returns:
True if the timer has been expired
Raises:
ResourceNotFound item not found
reset - resets lvar status
Set lvar status to 1 or start lvar timer
reset('lvar:tests/test1')
Parameters:
oid lvar OID
Raises:
FunctionFailed lvar value set error
ResourceNotFound lvar not found
set - sets lvar value
set('lvar:tests/test1', value=1)
Parameters:
oid lvar OID
Optionally:
value lvar value (if not specified, lvar is set to null)
Raises:
FunctionFailed lvar value set error
ResourceNotFound lvar not found
toggle - toggles lvar status
Change lvar status to opposite boolean (0->1, 1->0)
toggle('lvar:tests/test1')
Parameters:
oid lvar OID
Raises:
FunctionFailed lvar value set error
ResourceNotFound lvar not found
Unit control
action - executes unit control action
r = action('unit:tests/door', value=1, wait=5)
Parameters:
oid unit OID
value desired unit value
Optionally:
wait wait for the completion for the specified number of seconds
priority queue priority (default is 100, lower is better)
Returns:
Serialized action object (dict)
{
"err": null,
"exitcode": 0,
"finished": true,
"node": "mws1",
"oid": "unit:tests/door",
"out": null,
"params": {
"status": 1
},
"priority": 100,
"status": "completed",
"svc": "eva.controller.virtual",
"time": {
"accepted": 1651971627.5822825,
"completed": 1651971627.5823474,
"created": 1651971627.5794573
},
"uuid": "60202130-8c28-4632-a645-f840849ca144"
}
Raises:
FunctionFailed action failed to be executed
ResourceNotFound unit not found
action_toggle - executes an action to toggle unit status
Creates a unit control action to toggle its status (1->0, 0->1)
r = action_toggle('unit:tests/door', wait=5)
Parameters:
oid unit OID
Optionally:
value desired unit value
wait wait for the completion for the specified number of seconds
uuid action UUID (will be auto generated if none specified)
priority queue priority (default is 100, lower is better)
Returns:
Serialized action object (dict)
{
"err": null,
"exitcode": 0,
"finished": true,
"node": "mws1",
"oid": "unit:tests/door",
"out": null,
"params": {
"status": 1
},
"priority": 100,
"status": "completed",
"svc": "eva.controller.virtual",
"time": {
"accepted": 1651971627.5822825,
"completed": 1651971627.5823474,
"created": 1651971627.5794573
},
"uuid": "60202130-8c28-4632-a645-f840849ca144"
}
Raises:
ResourceNotFound unit not found
is_busy - checks is the unit busy
r = is_busy('tests/unit1')
print(r)
False
Parameters:
oid unit OID
Returns:
True if unit is busy (action is executed)
Raises:
ResourceNotFound unit not found
kill - kills unit actions
Terminates the current action (if possible) and cancels all pending
kill('unit:tests/unit1')
Parameters:
oid unit OID
Raises:
ResourceNotFound unit not found
result - gets action status
Checks the result of the action by its UUID or returns the actions for the specified unit
r = result('unit:tests/unit1')
Parameters:
oid unit OID or
uuid action uuid
Optionally:
sq filter by action status: waiting, running, completed, failed or finished
limit limit action list to N records
Returns:
list or single serialized action object
{
"err": null,
"exitcode": 0,
"finished": true,
"node": "mws1",
"oid": "unit:tests/door",
"out": null,
"params": {
"status": 1
},
"priority": 100,
"status": "completed",
"svc": "eva.controller.virtual",
"time": {
"accepted": 1651971627.5822825,
"completed": 1651971627.5823474,
"created": 1651971627.5794573
},
"uuid": "60202130-8c28-4632-a645-f840849ca144"
}
Raises:
ResourceNotFound unit or action not found
start - executes an action to a unit
Creates unit control action to set its value to 1
r = start('unit:tests/unit1', wait=5)
Parameters:
oid unit OID
Optionally:
wait wait for the completion for the specified number of seconds
priority queue priority (default is 100, lower is better)
Returns:
Serialized action object (dict)
Raises:
ResourceNotFound unit not found
stop - executes an action to stop a unit
Creates unit control action to set its value to 0
r = stop('unit:tests/unit1', wait=5)
Parameters:
oid unit OID
Optionally:
wait wait for the completion for the specified number of seconds
priority queue priority (default is 100, lower is better)
Returns:
Serialized action object (dict)
Raises:
ResourceNotFound unit not found
terminate - terminates action execution
Terminates or cancel the action if it is still queued
try:
terminate('unit:tests/unit1')
except ResourceNotFound:
print('no action running')
Parameters:
uuid action uuid
Raises:
ResourceNotFound if action is not found or action is already finished
Locking functions
lock - acquires a lock
Requires locker svc to be set in the controller config
lock('lock1', expires=1)
Parameters:
lock_id lock id
expires time after which the lock is automatically unlocked (sec)
Optionally:
timeout max timeout to wait
Raises:
FunctionFailed failed to acquire the lock
TimeoutException timed out
unlock - releases a lock
Releases the previously acquired lock
unlock('lock1')
Parameters:
lock_id lock id
Raises:
FunctionFailed ffailed to release the lock
Logging
Note
All logging functions accept multiple parameters as message parts. The parameters are concatenated with a space character.
trace - log trace message
Logs a message with trace level
trace('this is a test trace message')
debug - log debug message
Alias for logging.debug
debug('this is a test debug message')
info - log info message
Note
In Python macros, the default “print” function is alias for “info” as well.
info('this is a test debug message')
a = 123
info('the value is', a)
warning - log warning message
Alias for logging.warning
info('this is a test debug message')
error - log error message
Alias for logging.error
error('this is a test debug message')
critical - log critical message
Alias for logging.critical
critical('this is a test debug message')
report_accounting_event - reports an event into accounting system
report_accounting_event(subj='test', note='all is fine')
Optionally:
u user account name (string)
src source (e.g. IP address)
svc service ID (default: sender)
subj event subject
oid affected item OID
data a structure with any additional information
note a custom note (string)
code error code (0 = success)
err error message