Source code for automate.callables.builtin_callables

# -*- coding: utf-8 -*-
# (c) 2015 Tuomas Airaksinen
#
# This file is part of Automate.
#
# Automate is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Automate is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Automate.  If not, see <http://www.gnu.org/licenses/>.
#
# ------------------------------------------------------------------
#
# If you like Automate, please take a look at this page:
# http://evankelista.net/automate/

from __future__ import unicode_literals, division

import datetime
from builtins import bytes

from future import standard_library

standard_library.install_aliases()

import re
import threading
import xmlrpc.client
import socket
import subprocess

from http.client import HTTPException

from traits.api import (CList, Any, Property, Set, Bool, Event, CBool, on_trait_change, cached_property)

from automate.callable import AbstractCallable
from automate.common import deep_iterate, get_modules_all
from automate.statusobject import StatusObject
from automate.common import (threaded, thread_start, is_iterable)


[docs]class Empty(AbstractCallable): """ Do nothing but return None. Default action in Programs. Usage:: Empty() """ test = Bool(False) def call(self, caller, **kwargs): return None
[docs]class AbstractAction(AbstractCallable): """ Abstract base class for actions (i.e. callables that do something but do not necessarily return anything. """ status = Bool(True)
[docs]class Attrib(AbstractAction): """ Give specified attribute of a object. :param no_eval bool: if True, evaluation of object is skipped -- use this to access attributes of SystemObjects Usage & example:: Attrib(obj, 'attributename') Attrib(sensor_name, 'status', no_eval=True) """ @property def method(self): return self._args[1] def call(self, caller, **kwargs): no_eval = self._kwargs.get('no_eval', False) if not caller: return if no_eval: obj = self.obj else: obj = self.call_eval(self.obj, caller, **kwargs) attr = self.call_eval(self.method, caller, **kwargs) return getattr(obj, attr, None)
[docs]class Method(AbstractAction): """ Call method in an object with specified args Usage:: Method(obj, 'methodname') """ @property def method(self): return self._args[1] @property def args(self): try: return self._args[2:] except IndexError: return () def call(self, caller, **kwargs): if not caller: return kwargs = self._kwargs.copy() if not kwargs.pop('no_caller', False): arglist = [caller] + list(self.args) else: arglist = self.args return getattr(self.call_eval(self.obj, caller, **kwargs), self.call_eval(self.method, caller, **kwargs))(*arglist, **kwargs)
[docs]class Func(AbstractAction): """ Call function with given arguments. Usage & example:: Func(function, *args, **kwargs) Func(time.sleep, 2) :param bool add_caller: if True, then caller program is passed as first argument. """ @property def args(self): return tuple(self._args[1:]) def call(self, caller, **kwargs): if not caller: return _kwargs = {k: self.call_eval(v, caller, **kwargs) for k, v in list(self._kwargs.items())} return_value = _kwargs.pop('return_value', True) args = [self.call_eval(i, caller, return_value=return_value, **kwargs) for i in self.args] if _kwargs.pop('add_caller', False): arglist = [caller] + list(args) else: arglist = args self.logger.debug("Func %s %s %s", self.obj, arglist, _kwargs) try: return self.call_eval(self.obj, caller, **kwargs)(*arglist, **_kwargs) except Exception as e: self.logger.error('Exception occurred in %s: %s', self, e)
[docs]class OnlyTriggers(AbstractCallable): """ Baseclass for actions that do not have any targets (i.e. almost all actions). """ def _give_targets(self): return None
[docs]class Log(OnlyTriggers): """ Print callable argument outputs / other arguments to the log. Usage:: Log(object1, object2, 'string1'...) :param str log_level: Log level (i.e. logging function name) (default 'info') """ default_log_level = 'info' def call(self, caller, **kwargs): log_level = self._kwargs.get('log_level', self.default_log_level) l = [] for o in self.objects: l.append(self.call_eval(o, caller, **kwargs)) getattr(self.logger, log_level)(*l) return True
[docs]class Debug(Log): """ Same as :class:`.Log` but with debug logging level. """ default_log_level = 'debug'
[docs]class ToStr(OnlyTriggers): """ Return string representation of given arguments evaluated. Usage:: ToStr('formatstring {} {}', callable1, statusobject1) :param bool no_sub: if True, removes format string from argument list. Then usage is simply: .. code-block:: python ToStr(callable1, statusobject1, no_sub=True) """ def call(self, caller, **kwargs): _kwargs = self._kwargs.copy() no_sub = _kwargs.pop('no_sub', False) if no_sub: rv = ' '.join([str(self.call_eval(i, caller, **kwargs)) for i in self.objects]) else: rv = str(self.call_eval(self.obj, caller, **kwargs)).format( *[self.call_eval(i, caller, **kwargs) for i in self.objects[1:]], **_kwargs) return rv
[docs]class Eval(AbstractAction): """Execute python command given as a string with eval (or exec). Usage:: Eval("print time.{param}()", pre_exec="import time", param="time") First argument: python command to be evaluated. If it can be evaluated by eval() then return value is the evaluated value. Otherwise, exec() is used and True is returned. :param str pre_exec: pre-execution string. For example necessary import commands. :param dict namespace: Namespace. Defaults to locals() in :mod:`.builtin_callables`. Optionally, other keyword arguments can be given, and they are replaced in the first argument by format(). See also (and prefer using): :class:`.Func` """ def call(self, caller, **kwargs): if not caller: return _kwargs = self._kwargs.copy() namespace = _kwargs.pop('namespace', locals()) pre_exec = _kwargs.pop('pre_exec', '') if pre_exec: exec(pre_exec.format(**self._kwargs), namespace) try: return eval(self.obj.format(**self._kwargs), namespace) except SyntaxError: exec(self.obj.format(**self._kwargs), namespace) return True
[docs]class Exec(Eval): """Synonym to :class:`.Eval`"""
[docs]class GetService(AbstractAction): """ Get service by name and number. Usage:: GetService(name) GetService(name, number) Usage examples:: GetService('WebService') GetService('WebService', 1) """ def call(self, caller, **kwargs): name = self._args[0] num = self._args[1] if len(self._args) > 1 else 0 return self.system.services_by_name[name][num]
[docs]class ReloadService(AbstractAction): """ Reload given service. Usage:: ReloadService(name, number) ReloadService(name) Usage examples:: ReloadService('WebService', 0) ReloadService('ArduinoService') """ def call(self, caller, **kwargs): name = self._args[0] num = self._args[1] if len(self._args) > 1 else 0 return self.system.services_by_name[name][num].reload()
[docs]class Shell(AbstractAction): """ Execute shell command and return string value :param bool no_wait: if True, execute shell command in new thread and return pid :param bool output: if True, execute will return the output written to stdout by shell command. By default, execution status (integer) is returned. :param str input: if given, input is passed to stdin of the given shell command. Usage examples:: Shell('/bin/echo test', output=True) # returns 'test' Shell('mplayer something.mp3', no_wait=True) # returns PID of mplayer # process that keeps running Shell('/bin/cat', input='test', output=True) # returns 'test'. """ def call(self, caller, **kwargs): cmd = self.call_eval(self.obj, caller, **kwargs) input = self._kwargs.get('input', None) if input: input = bytes(self.call_eval(input, caller, **kwargs), 'utf-8') if not caller: return try: process = subprocess.Popen( cmd, executable='bash', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE) self.logger.debug('Shell: cmd "%s", pid %s', cmd, process.pid) if self._kwargs.get('no_wait', False): thread_start(self.system, lambda: process.communicate(input)) return process.pid else: out, err = process.communicate(input) retcode = process.poll() if self._kwargs.get('output', False): return out.decode('utf-8') else: return retcode except Exception as e: self.system.logger.error('Error %s in %s, cmd: %s', e, self, cmd) return -1
[docs]class SetStatus(AbstractAction): """ Set sensor or actuator value Usage:: SetStatus(target, source) # sets status of target to the status of source. SetStatus(target, source, Force=True) # sets status to hardware level even if it is not changed SetStatus([actuator1, actuator2], [sensor1, sensor2]) # sets status of actuator 1 to status of sensor1 and # status of actuator2 to status of sensor2. """ def call(self, caller=None, trigger=None, **kwargs): if not caller: return force = self._kwargs.get('force', False) values = self.call_eval(self.value, caller, **kwargs) objs = self.name_to_system_object(self.obj) if isinstance(objs, AbstractCallable): objs = self.call_eval(objs, caller, return_value=False, **kwargs) if not is_iterable(objs): objs = [objs] objs = list(objs) if not is_iterable(values): values = [values] * len(objs) values = list(values) if len(objs) != len(values): raise RuntimeError('SetStatus: length of targets should be equal to ' 'length of sources: %s != %s' % (len(objs), len(values))) self.system.logger.debug('SetStatus: objs %s, rv: %s', objs, values) for idx, obj in enumerate(objs): value = self.call_eval(values[idx], caller, **kwargs) obj = self.call_eval(obj, caller, return_value=False, trigger=trigger, **kwargs) self.system.logger.debug('SetStatus(%s, %s) by %s', obj, value, caller) try: obj.set_status(value, origin=caller, force=force) except ValueError as e: self.system.logger.error( 'Trying to set invalid status %s of type %s (by %s). Error: %s', value, type(value), caller, e) except AttributeError as e: self.system.logger.error( 'Trying to set status of invalid object %s of type %s, by %s. Error: %s', obj, type(obj), caller, e) return True def _give_triggers(self): return self.value def _give_targets(self): return self.obj
[docs]class SetAttr(AbstractAction): """Set object's attributes Usage:: SetAttr(obj, attr=value, attr2=value2) # performs setattr(obj, attr, value) and setattr(obj, attr2, value2). """ def __init__(self, obj, **kwargs): super(SetAttr, self).__init__(obj, **kwargs) def call(self, caller, **kwargs): if not caller: return obj = self.obj for attr, val in list(self._kwargs.items()): val = self.call_eval(val, caller, **kwargs) setattr(obj, attr, val) return True def _give_triggers(self): return list(self._kwargs.values()) def _give_targets(self): return self.obj
[docs]class Changed(AbstractCallable): """ Is value changed since evaluated last time? If this is the first time this Callable is called (i.e. comparison to last value cannot be made), return True. Usage:: Changed(sensor1) """ _lastval = Any(transient=True) def call(self, caller, **kwargs): if not caller: return with self._lock: newval = self.call_eval(self.obj, caller, **kwargs) rval = self._lastval != newval self.logger.debug("Changed(%s) last:%s new:%s: rval:%s", caller, self._lastval, newval, rval) self._lastval = newval return rval def _give_targets(self): return None
[docs]class Swap(AbstractAction): """ Swap sensor or BinaryActuator status (False to True and True to False) Usage:: Swap(actuator1) """ def call(self, caller, trigger=None, **kwargs): if not caller: return rv = not (self.call_eval(self.obj, caller, **kwargs)) self.logger.debug("Swap(%s): rv:%s", caller, rv) if trigger == self.obj: self.logger.debug('Swap: ignoring setting trigger value') return False self.obj.set_status(rv, caller) return rv def _give_triggers(self): return None
[docs]class AbstractRunner(AbstractAction): """ Abstract baseclass for Callables that are used primarily to run other Actions """
[docs]class Run(AbstractRunner): """Run specified Callables one at time. Return always True. Usage:: Run(callable1, callable2, ...) """ def call(self, caller=None, **kwargs): if not caller: return for i in self.objects: self.call_eval(i, caller, **kwargs) return True
[docs]class Delay(AbstractRunner): """Execute commands delayed by time (in seconds) in separate thread Usage:: Delay(delay_in_seconds, action) """ @property def delay(self): return self._args[0] @property def objects(self): return self._args[1:] def call(self, caller, **kwargs): if not caller: return with self._lock: state = self.get_state(caller) timers = state.get_or_create('timers', []) self.logger.info("Scheduling %s", self) delay = self.call_eval(self.delay, caller, **kwargs) timer = threading.Timer(delay, None) timer.function = threaded(self.system, self._run, caller, timer, **kwargs) time_after_delay = datetime.datetime.now() + datetime.timedelta(seconds=delay) timer.name = "Timer for %s timed at %s (%d sek)" % (self, time_after_delay, delay) timer.start() timers.append(timer) def cancel(self, caller): with self._lock: state = self.get_state(caller) timers = state.get('timers', []) for timer in timers: if timer.is_alive(): self.logger.info("Cancelling %s", self) timer.cancel() self.del_state(caller) super(Delay, self).cancel(caller) def _run(self, caller, timer, **kwargs): self.logger.info("Time is up, running %s", self) for i in self.objects: if not caller in self.state: # cancelled return self.call_eval(i, caller, **kwargs) with self._lock: if caller in self.state: # if not cancelled self.get_state(caller).timers.remove(timer) return True
[docs]class Threaded(Delay): """Execute commands in a single thread (in order) Usage:: Threaded(action) """ def __init__(self, *args, **kwargs): super(Threaded, self).__init__(0, *args, **kwargs)
[docs]class If(AbstractCallable): """Basic If statement Usage:: If(x, y, z) # if x, then run y, z, where x, y, and z are Callables or StatusObjects If(x, y) """ def call(self, caller=None, **kwargs): if self.call_eval(self.objects[0], caller, **kwargs): objs = self.objects[1:] if len(objs) > 1: for i in objs: self.call_eval(i, caller, **kwargs) return True else: return self.call_eval(self.objects[1], caller, **kwargs) else: return False def _give_triggers(self): return self.objects[1:] def _give_targets(self): return self.objects[1:]
[docs]class IfElse(AbstractCallable): """ Basic if - then - else statement Usage:: IfElse(x, y, z) # if x, then run y, else run z, where x, y, # and z are Callables or StatusObjects IfElse(x, y) """ def call(self, caller=None, **kwargs): if self.call_eval(self.objects[0], caller, **kwargs): return self.call_eval(self.objects[1], caller, **kwargs) if len(self.objects) > 2: return self.call_eval(self.objects[2], caller, **kwargs) else: return False def _give_triggers(self): return self.objects[1:] def _give_targets(self): return self.objects[1:]
[docs]class Switch(AbstractCallable): """ Basic switch - case statement. Two alternative usages: * First argument switch criterion (integer-valued) and others are cases **OR** * First argument is switch criterion and second argument is dictionary that contains all possible cases as keys and related actions as their values. Usage:: Switch(criterion, choice1, choice2...) # where criteria is integer-valued # (Callable or StatusObject etc.) # and choice1, 2... are Callables. Switch(criterion, {'value1': callable1, 'value2': 'callable2'}) """ def call(self, caller=None, **kwargs): sel = self.call_eval(self.objects[0], caller, **kwargs) if isinstance(self.value, dict): return self.call_eval(self.value.get(sel, None), caller, **kwargs) else: return self.call_eval(self.objects[sel + 1], caller, **kwargs) def _give_targets(self): return deep_iterate(self.objects[1:]) def _give_triggers(self): return deep_iterate(self.objects[1:])
[docs]class TryExcept(AbstractRunner): """Try returning `x`, but if exception occurs in the value evaluation, then return `y`. Usage:: Try(x, y) # where x and y are Callables or StatusObjects etc. """ def call(self, caller=None, **kwargs): try: return self.call_eval(self.objects[0], caller, **kwargs) except: return self.call_eval(self.objects[1], caller, **kwargs)
class AbstractMathematical(AbstractCallable): def _give_targets(self): return None
[docs]class Min(AbstractMathematical): """ Give minimum number of given objects. Usage:: Min(x, y, z...) # where x,y,z are anything that can be # evaluated as number (Callables, Statusobjects etc). """ def call(self, caller=None, **kwargs): val = float("inf") for i in self.objects: val = min(val, self.call_eval(i, caller, **kwargs)) return val
[docs]class Max(AbstractMathematical): """Give maximum number of given objects Usage:: Max(x, y, z...) # where x,y,z are anything that can be # evaluated as number (Callables, Statusobjects etc). """ def call(self, caller=None, **kwargs): val = -float("inf") for i in self.objects: val = max(val, self.call_eval(i, caller, **kwargs)) return val
[docs]class Sum(AbstractMathematical): """Give sum of given objects Usage:: Sum(x, y, z...) # where x,y,z are anything that can be # evaluated as number (Callables, Statusobjects etc). """ def call(self, caller=None, **kwargs): _sum = 0.0 for i in self.objects: _sum += self.call_eval(i, caller, **kwargs) return _sum
[docs]class Product(AbstractMathematical): """Give product of given objects Usage:: Product(x, y, z...) # where x,y,z are anything that can be # evaluated as number (Callables, Statusobjects etc). """ def call(self, caller=None, **kwargs): _sum = 1.0 for i in self.objects: _sum *= self.call_eval(i, caller, **kwargs) return _sum
[docs]class Mult(Product): """Synonym of Product"""
[docs]class Division(AbstractMathematical): """Give division of given objects Usage:: Division(x, y) # where x,y are anything that can be # evaluated as number (Callables, Statusobjects etc). """ def call(self, caller=None, **kwargs): if len(self.objects) != 2: raise RuntimeError('Wrong amount of arguments') obj1, obj2 = self.objects _val1 = self.call_eval(obj1, caller, **kwargs) _val2 = self.call_eval(obj2, caller, **kwargs) return _val1 / _val2
[docs]class Div(Division): """ Synonym of Division"""
[docs]class Add(Sum): """Synonym of Sum """
[docs]class AbstractLogical(AbstractMathematical): """Abstract class for logic operations (:class:`.And`, :class:`.Or` etc.) """
[docs]class Anything(AbstractLogical): """ Condition which gives `True` always Usage:: Anything(x,y,z...) """ def call(self, caller=None, **kwargs): return True
[docs]class Or(AbstractLogical): """ Or condition Usage:: Or(x,y,z...) # gives truth value of x or y or z or ,,, """ def call(self, caller=None, **kwargs): def _or(list): for i in list: val = self.call_eval(i, caller, **kwargs) if is_iterable(val): val = _or(val) if val: return True return False return _or(self.objects)
[docs]class And(AbstractLogical): """And condition Usage:: And(x,y,z...) # gives truth value of x and y and z and ... """ def call(self, caller=None, **kwargs): def _and(list): for i in list: val = self.call_eval(i, caller, **kwargs) if is_iterable(val): val = _and(val) if not val: return False return True return _and(self.objects)
[docs]class Neg(AbstractLogical): """Give negative of specified callable (minus sign) Usage:: Neg(x) # returns -x """ def call(self, caller=None, **kwargs): if len(self.objects) != 1: raise RuntimeError('Too many arguments') return -self.call_eval(self.obj, caller, **kwargs)
[docs]class Inverse(AbstractLogical): """Give inverse of specified callable (1/something) Usage:: Inv(x) # returns 1/x """ def call(self, caller=None, **kwargs): if len(self.objects) != 1: raise RuntimeError('Too many arguments') return 1./self.call_eval(self.obj, caller, **kwargs)
[docs]class Inv(Inverse): """ Synonym of Inverse """
[docs]class Not(AbstractLogical): """Give negation of specified object Usage:: Not(x) # returns not x """ def call(self, caller=None, **kwargs): if len(self.objects) != 1: raise RuntimeError('Too many arguments') return not self.call_eval(self.obj, caller, **kwargs)
[docs]class Equal(AbstractLogical): """Equality condition, i.e. is x == y Usage:: Equal(x, y) # returns truth value of x == y """ def call(self, caller=None, **kwargs): return self.call_eval(self.obj, caller, **kwargs) == self.call_eval(self.value, caller, **kwargs)
[docs]class Less(AbstractLogical): """Condition: is x < y Usage:: Less(x,y) # returns truth value of x < y """ def call(self, caller=None, **kwargs): a = self.call_eval(self.obj, caller, **kwargs) b = self.call_eval(self.value, caller, **kwargs) try: rv = a < b except TypeError: rv = False return rv
[docs]class More(AbstractLogical): """Condition: is x > y Usage:: More(x,y) # returns truth value of x > y """ def call(self, caller=None, **kwargs): a = self.call_eval(self.obj, caller, **kwargs) b = self.call_eval(self.value, caller, **kwargs) try: rv = a > b except TypeError: rv = False return rv
[docs]class Value(AbstractLogical): """Give specified value Usage:: Value(x) # returns value of x. Used to convert StatusObject into Callable, # for example, if StatusObject status needs to be used directly # as a condition of Program condition attributes. """ _args = CList def call(self, caller=None, **kwargs): return self.call_eval(self.obj, caller, **kwargs)
[docs]class AbstractQuery(AbstractCallable): """ Baseclass for query type of Callables, i.e. those that return set of objects from system based on given conditions. """
class ReprObject(object): def __init__(self, name): self.name = name def __repr__(self): return self.name
[docs]class OfType(AbstractQuery): """ Gives all objects of given type that are found in System Usage & example:: OfType(type, **kwargs) OfType(AbstractActuator, exclude=['actuator1', 'actuator2']) # returns all actuators in system, except those named 'actuator1' and 'actuator2'. :param list exclude: list of instances to be excluded from the returned list. """ system_objects_changed = Event triggers = Property(trait=Set(trait=StatusObject), depends_on='on_setup_callable, _kwargs_items, _args_items, system_objects_changed') targets = Property(trait=Set(trait=StatusObject), depends_on='on_setup_callable, _kwargs, _kwargs_items, _args, _args_items, ' 'system_objects_changed') setup_complete = CBool(transient=True) @on_trait_change('system') def set_setup_complete(self, name, new): if name == 'system': self.system.on_trait_change(self.set_setup_complete, 'post_init_trigger') if name == 'post_init_trigger': self.setup_complete = True @on_trait_change('system.objects, system.objects_items, setup_complete') def trigger_system_objects_changed(self): if self.setup_complete: self.system_objects_changed = 1 def _both(self): if not self.system: return set() exclude = set(self._kwargs.get('exclude', [])) # exclude must be list type return {i for i in self.system.objects if isinstance(i, tuple(self.objects))} - exclude @cached_property def _get_triggers(self): if self._kwargs.get('type', 'both') in ['triggers', 'both']: return self._both() return set() @cached_property def _get_targets(self): if self._kwargs.get('type', 'both') in ['targets', 'both']: return self._both() return set() def call(self, caller=None, **kwargs): return self.targets def give_str(self): args = [ReprObject(i.__name__) for i in self.objects] return self._give_str(args, self._kwargs) def give_str_indented(self, tags=False): args = [ReprObject(i.__name__) for i in self.objects] return self._give_str_indented(args, self._kwargs, tags)
[docs]class RegexSearch(AbstractCallable): """ Scan through string looking for a match to the pattern. Return matched parts of string by :func:`re.search`. :param int group: Match group can be chosen by group number. Usage & examples:: RegexSearch(match_string, content_to_search, **kwargs) RegexSearch(r'(\d*)(\w*)', '12test') # returns '12' RegexSearch(r'(\d*)(\w*)', '12test', group=2) # returns 'test' RegexSearch(r'testasfd', 'test') # returns '' .. tip:: More examples in unit tests """ def call(self, caller=None, **kwargs): pattern = str(self.call_eval(self.obj, caller, **kwargs)) searchstring = str(self.call_eval(self.value, caller, **kwargs)) match = re.search(pattern, searchstring, re.MULTILINE) if match: return match.group(self._kwargs.get('group', 1)) else: return ''
[docs]class RegexMatch(AbstractCallable): """ Try to apply the pattern at the start of the string. Return matched parts of string by :func:`re.match`. :param int group: Match group can be chosen by group number. Usage & examples from unit tests:: RegexMatch(match_string, content_to_search, **kwargs) RegexMatch(r'heptest', 'heptest') # returns 'heptest' RegexMatch(r'heptest1', 'heptest') # returns '' RegexMatch(r'(hep)te(st1)', 'heptest1', group=1) # returns 'hep' RegexMatch(r'(hep)te(st1)', 'heptest1', group=2) # returns 'st1' .. tip:: More examples in unit tests """ def call(self, caller=None, **kwargs): pattern = self.call_eval(self.obj, caller, **kwargs) searchstring = self.call_eval(self.value, caller, **kwargs) match = re.match(pattern, searchstring, re.MULTILINE) if match: return match.group(self._kwargs.get('group', 0)) else: return ''
[docs]class RemoteFunc(AbstractCallable): """ Evaluate remote function via XMLRPC. Usage:: RemoteFunc('host', 'funcname', *args, **kwargs) """ _cached_server = Any(transient=True) def call(self, caller, **kwargs): try: if self._cached_server is None: host = self.call_eval(self.obj, caller, **kwargs) self._cached_server = server = xmlrpc.client.ServerProxy(host) else: server = self._cached_server funcname = self.call_eval(self.value, caller, **kwargs) args = [self.call_eval(o, caller, **kwargs) for o in self.objects[2:]] _kwargs = {k: self.call_eval(v, caller, **kwargs) for k, v in list(self._kwargs.items())} try: return getattr(server, funcname)(*args, **_kwargs) except (xmlrpc.client.Fault, HTTPException) as e: self.logger.exception( 'Exception occurred in remote function call (%s,%s)(*%s, **%s), error: %s', server, funcname, args, _kwargs, e) except (socket.gaierror, IOError, xmlrpc.client.Fault) as e: self.logger.error('Could call remote function, error: %s', e)
[docs]class WaitUntil(AbstractRunner): """ Wait until sensor/actuator/callable status changes to True and then execute commands. WaitUntil will return immediately and only execute specified actions after criteria is fullfilled. Usage:: WaitUntil(sensor_or_callable, Action1, Action2, etc) .. note:: No triggers are collected from WaitUntil """ def call(self, caller, **kwargs): obj = self.name_to_system_object(self.obj) def callback(**kwargs2): nocallback = kwargs2.get('nocallback', False) callbacks = self.get_state(caller).callbacks if not nocallback else [] if obj.status and (nocallback or (callbacks and callback in callbacks)): for i in self.objects[1:]: self.call_eval(i, caller, **kwargs) if not nocallback: self.logger.debug('Removing executed callback') with self._lock: obj.on_trait_change(callback, 'status', remove=True) callbacks.remove(callback) if not obj.status: self.logger.debug('Adding callback') obj.on_trait_change(callback, 'status') with self._lock: callbacks = self.get_state(caller).get_or_create('callbacks', []) callbacks.append(callback) else: self.logger.debug('Calling directly, no callback') callback(nocallback=True) def cancel(self, caller): self.logger.debug('Cancelling callbacks') with self._lock: state = self.get_state(caller) callbacks = state.get('callbacks', []) for cb in callbacks: self.obj.on_trait_change(cb, 'status', remove=True) self.del_state(caller) super(WaitUntil, self).cancel(caller) def _give_triggers(self): return None
[docs]class While(AbstractRunner): """ Executes commands (in thread) as long as criteria (sensor, actuator, callable status) remains true. Flushes worker queue between each iteration such that criteria is updated, if executed actions alter it. :param Callable do_after: given Callable is executed after while loop is finished. Usage & example:: While(criteria, action1, action2, do_after=action3) # Example loop that runs actions 10 times. Assumes s=UserIntSensor() Run( SetStatus(s, 0), While(s < 10, SetStatus(s, s+1), other_actions ) ) .. note:: While execution is performed in separate thread .. note:: No triggers are collected from While """ class ExitThread(Exception): pass def _run(self, caller, thread, **kwargs): self.system.flush() try: while self.call_eval(self.obj, caller, **kwargs): for i in self.objects[1:]: if thread._cancel_while: raise self.ExitThread self.call_eval(i, caller, **kwargs) self.system.flush() do_after = self._kwargs.get('do_after') if do_after: self.call_eval(do_after, caller, **kwargs) except self.ExitThread: self.logger.debug('While exited via cancel') with self._lock: state = self.get_state(caller) state.threads.remove(thread) if not state.threads: self.logger.debug('Last thread, removing state') self.del_state(caller) def call(self, caller, **kwargs): with self._lock: state = self.get_state(caller) threads = state.get_or_create('threads', []) t = threading.Timer(0., None) t.function = threaded(self.system, self._run, caller, t, **kwargs) t.name = 'Thread for %s' % self t._cancel_while = False threads.append(t) t.start() return True def cancel(self, caller): self.logger.debug('Canceling While') with self._lock: state = self.get_state(caller) for t in state.get('threads', []): t._cancel_while = True super(While, self).cancel(caller) def _give_triggers(self): return None
[docs]class TriggeredBy(OnlyTriggers): """ Return whether action was triggered by one of specified triggers or not If no arguments, return the trigger. Usage:: TriggeredBy() # -> returns the trigger TriggeredBy(trig1, trig2...) #-> Returns if trigger is one of arguments """ def call(self, caller=None, trigger=None, **kwargs): if self.objects: obj_eval = [self.name_to_system_object(obj) for obj in self.objects] return trigger in obj_eval else: return trigger
__all__ = get_modules_all(AbstractCallable, locals())