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 <bjoern_pedersen@frm2.tum.de> Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
This commit is contained in:
26
etc/sim.cfg
Normal file
26
etc/sim.cfg
Normal file
@ -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
|
||||||
|
|
@ -29,7 +29,6 @@
|
|||||||
import time
|
import time
|
||||||
import types
|
import types
|
||||||
import inspect
|
import inspect
|
||||||
import threading
|
|
||||||
|
|
||||||
from secop.lib import formatExtendedStack, mkthread
|
from secop.lib import formatExtendedStack, mkthread
|
||||||
from secop.lib.parsing import format_time
|
from secop.lib.parsing import format_time
|
||||||
@ -338,6 +337,7 @@ class Module(object):
|
|||||||
if k[1:] in self.PROPERTIES:
|
if k[1:] in self.PROPERTIES:
|
||||||
self.PROPERTIES[k[1:]] = v
|
self.PROPERTIES[k[1:]] = v
|
||||||
del cfgdict[k]
|
del cfgdict[k]
|
||||||
|
|
||||||
# derive automatic properties
|
# derive automatic properties
|
||||||
mycls = self.__class__
|
mycls = self.__class__
|
||||||
myclassname = '%s.%s' % (mycls.__module__, mycls.__name__)
|
myclassname = '%s.%s' % (mycls.__module__, mycls.__name__)
|
||||||
@ -372,6 +372,7 @@ class Module(object):
|
|||||||
'Module %s:config Parameter %r '
|
'Module %s:config Parameter %r '
|
||||||
'not unterstood! (use on of %r)' %
|
'not unterstood! (use on of %r)' %
|
||||||
(self.name, k, self.PARAMS.keys()))
|
(self.name, k, self.PARAMS.keys()))
|
||||||
|
|
||||||
# complain if a PARAM entry has no default value and
|
# complain if a PARAM entry has no default value and
|
||||||
# is not specified in cfgdict
|
# is not specified in cfgdict
|
||||||
for k, v in self.PARAMS.items():
|
for k, v in self.PARAMS.items():
|
||||||
@ -404,7 +405,6 @@ class Module(object):
|
|||||||
# raise ConfigError('Module %s: config parameter %r:\n%r' %
|
# raise ConfigError('Module %s: config parameter %r:\n%r' %
|
||||||
# (self.name, k, e))
|
# (self.name, k, e))
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
self._requestLock = threading.RLock()
|
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
# may be overriden in derived classes to init stuff
|
# may be overriden in derived classes to init stuff
|
||||||
@ -440,9 +440,7 @@ class Readable(Module):
|
|||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
Module.init(self)
|
Module.init(self)
|
||||||
self._pollthread = threading.Thread(target=self.__pollThread)
|
self._pollthread = mkthread(self.__pollThread)
|
||||||
self._pollthread.daemon = True
|
|
||||||
self._pollthread.start()
|
|
||||||
|
|
||||||
def __pollThread(self):
|
def __pollThread(self):
|
||||||
"""super simple and super stupid per-module polling thread"""
|
"""super simple and super stupid per-module polling thread"""
|
||||||
|
113
secop/simulation.py
Normal file
113
secop/simulation.py
Normal file
@ -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 <enrico.faulhaber@frm2.tum.de>
|
||||||
|
#
|
||||||
|
# *****************************************************************************
|
||||||
|
"""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, ''
|
Reference in New Issue
Block a user