From b0c3ac01d42ec3d504e069a6b1ec429d53be8105 Mon Sep 17 00:00:00 2001 From: Enrico Faulhaber Date: Fri, 1 Dec 2017 15:05:45 +0100 Subject: [PATCH] Provide a mean for simulation dummy devices create the correct descriptive data. Main use case is for unit-tests and testing custom configs. Change-Id: I3077f80cb9fdbf2443ee9da796c3749707fd2b55 Reviewed-on: https://forge.frm2.tum.de/review/16806 Tested-by: JenkinsCodeReview Reviewed-by: Enrico Faulhaber --- etc/sim.cfg | 26 ++++++++++ secop/modules.py | 8 ++-- secop/simulation.py | 113 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 etc/sim.cfg create mode 100644 secop/simulation.py diff --git a/etc/sim.cfg b/etc/sim.cfg new file mode 100644 index 0000000..21cd177 --- /dev/null +++ b/etc/sim.cfg @@ -0,0 +1,26 @@ +[node test config] +description=description of the simulation sec-node + . + Testing simulation dummy setup. + +[interface tcp] +interface=tcp +bindto=0.0.0.0 +bindport=10767 +# protocol to use for this interface +framing=eol +encoding=secop + + +[device sim] +class=secop.simulation.SimDrivable +.description=simulation stuff +.extra_params=param3,param4,jitter,ramp +param3.datatype=['bool'] +param3.default=True +param3.readonly=False +jitter.default=1 +ramp.default=60 +value.default=123 +target.default=42 + diff --git a/secop/modules.py b/secop/modules.py index 447aa03..ffa1ca5 100644 --- a/secop/modules.py +++ b/secop/modules.py @@ -29,7 +29,6 @@ import time import types import inspect -import threading from secop.lib import formatExtendedStack, mkthread from secop.lib.parsing import format_time @@ -338,6 +337,7 @@ class Module(object): if k[1:] in self.PROPERTIES: self.PROPERTIES[k[1:]] = v del cfgdict[k] + # derive automatic properties mycls = self.__class__ myclassname = '%s.%s' % (mycls.__module__, mycls.__name__) @@ -372,6 +372,7 @@ class Module(object): 'Module %s:config Parameter %r ' 'not unterstood! (use on of %r)' % (self.name, k, self.PARAMS.keys())) + # complain if a PARAM entry has no default value and # is not specified in cfgdict for k, v in self.PARAMS.items(): @@ -404,7 +405,6 @@ class Module(object): # raise ConfigError('Module %s: config parameter %r:\n%r' % # (self.name, k, e)) setattr(self, k, v) - self._requestLock = threading.RLock() def init(self): # may be overriden in derived classes to init stuff @@ -440,9 +440,7 @@ class Readable(Module): def init(self): Module.init(self) - self._pollthread = threading.Thread(target=self.__pollThread) - self._pollthread.daemon = True - self._pollthread.start() + self._pollthread = mkthread(self.__pollThread) def __pollThread(self): """super simple and super stupid per-module polling thread""" diff --git a/secop/simulation.py b/secop/simulation.py new file mode 100644 index 0000000..d90f6da --- /dev/null +++ b/secop/simulation.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +# ***************************************************************************** +# +# This program 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 2 of the License, or (at your option) any later +# version. +# +# This program 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 +# this program; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Module authors: +# Enrico Faulhaber +# +# ***************************************************************************** +"""Define Simulation classes""" + +import random +from time import sleep + +from secop.modules import Module, Readable, Writable, Drivable, PARAM +from secop.lib import mkthread +from secop.protocol import status +from secop.datatypes import FloatRange + + +class SimBase(object): + def __init__(self, cfgdict): + # spice up PARAMS if requested by extra property + # hint: us a comma-separated list if mor than one extra_param + # BIG FAT WARNING: changing extra params will NOT generate events! + # XXX: implement default read_* and write_* methods to handle + # read and change messages correctly + if '.extra_params' in cfgdict: + extra_params = cfgdict.pop('.extra_params') + for k in extra_params.split(','): + self.PARAMS[k] = PARAM('extra_param: %s' % k.strip(), + datatype=FloatRange(), + default=0.0) + def late_init(self): + self._sim_thread = mkthread(self._sim) + + def _sim(self): + try: + while not self.sim(): + pass + except Exception as e: + self.log.exception(e) + self.log.info('sim thread ended') + + def sim(self): + return True + + def read_value(self, maxage=0): + if 'jitter' in self.PARAMS: + return self._value + self.jitter*(0.5-random.random()) + return self._value + + +class SimModule(SimBase, Module): + def __init__(self, logger, cfgdict, devname, dispatcher): + SimBase.__init__(self, cfgdict) + Module.__init__(self, logger, cfgdict, devname, dispatcher) + + +class SimReadable(SimBase, Readable): + def __init__(self, logger, cfgdict, devname, dispatcher): + SimBase.__init__(self, cfgdict) + Readable.__init__(self, logger, cfgdict, devname, dispatcher) + self._value = self.PARAMS['value'].default + + +class SimWritable(SimBase, Writable): + def __init__(self, logger, cfgdict, devname, dispatcher): + SimBase.__init__(self, cfgdict) + Writable.__init__(self, logger, cfgdict, devname, dispatcher) + self._value = self.PARAMS['value'].default + + +class SimDrivable(SimBase, Drivable): + def __init__(self, logger, cfgdict, devname, dispatcher): + SimBase.__init__(self, cfgdict) + Drivable.__init__(self, logger, cfgdict, devname, dispatcher) + self._value = self.PARAMS['value'].default + + def sim(self): + while self._value == self.target: + sleep(0.3) + self.status = status.BUSY, 'MOVING' + speed = 0 + if 'ramp' in self.PARAMS: + speed = self.ramp / 60. # ramp is per minute! + elif 'speed' in self.PARAMS: + speed = self.speed + if speed == 0: + self._value = self.target + speed *= 0.3 + + while self._value != self.target: + if self._value < self.target - speed: + self._value += speed + elif self._value > self.target + speed: + self._value -= speed + else: + self._value = self.target + sleep(0.3) + self.status = status.OK, ''