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
}

decrement_shared - decrements value of the shared variable

Decrements value of the variable, shared between controller macros. Initial value must be number

decrement_shared('counter1')

Parameters:

  • name variable name

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

increment_shared - increments value of the shared variable

Increments value of the variable, shared between controller macros. Initial value must be number

increment_shared('counter1')

Parameters:

  • name variable name

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_shared - sets value of the shared variable

Sets value of the variable, shared between controller macros

set_shared('var1', 777)

Parameters:

  • name variable name

Optionally:

  • value value to set. If empty, the variable is deleted

sha256sum - calculates SHA256 sum

Parameters:

  • value value to calculate

  • hexdigest return binary digest or hex (True, default)

Returns:

sha256 digest

shared - gets value of the shared variable

Gets value of the variable, shared between controller macros

r = shared('var1')
print(r)

777

Parameters:

  • name variable name

Optionally:

  • default value if variable doesn’t exist

Returns:

variable value, None (or default) if variable doesn’t exist

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 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

debug - log debug message

Alias for logging.debug

debug('this is a test debug message')

info - log info message

Alias for logging.info

Note

In Python macros, the default “print” function is alias for logging.info as well.

info('this is a test debug message')

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