check for problematic value range

A common problematic practice is, to declare the value parameter
with the same FloatRange than the target. Because of measurement
errors, a value might be near, but outside the limit.
In order to avoid this, we force the programmer to declare a
bigger range for the value than for the target, or to
explicitly disable this check on a module property.
It is also fine to declare the value without limits.

This behavior may be disabled via command line option or in the
general config file. For simplicity, FloatRanges inside data
structures are not considered.

+ above command line option is also used to disable the error
  handling on a string to float conversion
+ log appropriate error message for string to float conversion

Change-Id: Ib78ea1fb7c821442bf5847030573c8c27822dea5
Reviewed-on: https://forge.frm2.tum.de/review/c/sine2020/secop/playground/+/27574
Tested-by: Jenkins Automated Tests <pedersen+jenkins@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
Reviewed-by: Markus Zolliker <markus.zolliker@psi.ch>
This commit is contained in:
2022-01-28 17:41:53 +01:00
parent 26a0f2e078
commit b911bc1838
6 changed files with 160 additions and 53 deletions

View File

@ -29,10 +29,10 @@ from collections import OrderedDict
from functools import wraps
from secop.datatypes import ArrayOf, BoolType, EnumType, FloatRange, \
IntRange, StatusType, StringType, TextType, TupleOf
IntRange, StatusType, StringType, TextType, TupleOf, DiscouragedConversion
from secop.errors import BadValueError, ConfigError, \
ProgrammingError, SECoPError, SilentError, secop_error
from secop.lib import formatException, mkthread, UniqueObject
from secop.lib import formatException, mkthread, UniqueObject, generalConfig
from secop.lib.enum import Enum
from secop.params import Accessible, Command, Parameter
from secop.poller import BasicPoller, Poller
@ -40,6 +40,8 @@ from secop.properties import HasProperties, Property
from secop.logging import RemoteLogHandler, HasComlog
generalConfig.defaults['disable_value_range_check'] = False # check for problematic value range by default
Done = UniqueObject('already set')
"""a special return value for a read/write function
@ -475,9 +477,13 @@ class Module(HasAccessibles):
changed = pobj.value != value
try:
# store the value even in case of error
# TODO: we should neither check limits nor convert string to float here
pobj.value = pobj.datatype(value)
except Exception as e:
if isinstance(e, DiscouragedConversion):
if DiscouragedConversion.log_message:
self.log.error(str(e))
self.log.error('you may disable this behaviour by running the server with --relaxed')
DiscouragedConversion.log_message = False
if not err: # do not overwrite given error
err = e
if err:
@ -682,10 +688,30 @@ class Readable(Module):
class Writable(Readable):
"""basic writable module"""
disable_value_range_check = Property('disable value range check', BoolType(), default=False)
target = Parameter('target value of the module',
default=0, readonly=False, datatype=FloatRange(unit='$'))
def __init__(self, name, logger, cfgdict, srv):
super().__init__(name, logger, cfgdict, srv)
value_dt = self.parameters['value'].datatype
target_dt = self.parameters['target'].datatype
try:
# this handles also the cases where the limits on the value are more
# restrictive than on the target
target_dt.compatible(value_dt)
except Exception:
if type(value_dt) == type(target_dt):
raise ConfigError('the target range extends beyond the value range') from None
raise ProgrammingError('the datatypes of target and value are not compatible') from None
if isinstance(value_dt, FloatRange):
if (not self.disable_value_range_check and not generalConfig.disable_value_range_check
and value_dt.problematic_range(target_dt)):
self.log.error('the value range must be bigger than the target range!')
self.log.error('you may disable this error message by running the server with --relaxed')
self.log.error('or by setting the disable_value_range_check property of the module to True')
raise ConfigError('the value range must be bigger than the target range')
class Drivable(Writable):
"""basic drivable module"""