From 8ee49caba5dd28a75122f217297b7fa28855ffbf Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Fri, 10 Jan 2025 14:20:29 +0100 Subject: [PATCH] equipment_id for merged configs and routed nodes Add a new custom module property 'original_id' indicating the equipment_id the modules originally belongs to. This property is only given, when distinct from the equipment_id of the SEC node. It happens when multiple config files are given, for all modules but the ones given in the first file, and for routed modules, when multiple nodes are routed or own modules are given. + fix an issue in router: additional modules were ignore in case of a single node. + small cosmetic changes in config.py reducing IDE complains Change-Id: If846c47a06158629cef807d22b91f69e4f416563 Reviewed-on: https://forge.frm2.tum.de/review/c/secop/frappy/+/35396 Tested-by: Jenkins Automated Tests Reviewed-by: Markus Zolliker --- frappy/config.py | 8 +++++++- frappy/modulebase.py | 2 ++ frappy/protocol/router.py | 29 +++++++++++++++++------------ test/test_modules.py | 2 +- 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/frappy/config.py b/frappy/config.py index f157fcc..85586c1 100644 --- a/frappy/config.py +++ b/frappy/config.py @@ -56,10 +56,12 @@ class Param(dict): kwds['value'] = value super().__init__(**kwds) + class Group(tuple): def __new__(cls, *args): return super().__new__(cls, args) + class Mod(dict): def __init__(self, name, cls, description, **kwds): super().__init__( @@ -70,7 +72,8 @@ class Mod(dict): # matches name from spec if not re.match(r'^[a-zA-Z]\w{0,62}$', name, re.ASCII): - raise ConfigError(f'Not a valid SECoP Module name: "{name}". Does it only contain letters, numbers and underscores?') + raise ConfigError(f'Not a valid SECoP Module name: "{name}".' + ' Does it only contain letters, numbers and underscores?') # Make parameters out of all keywords groups = {} for key, val in kwds.items(): @@ -85,6 +88,7 @@ class Mod(dict): for member in members: self[member]['group'] = group + class Collector: def __init__(self, cls): self.list = [] @@ -120,12 +124,14 @@ class Config(dict): def merge_modules(self, other): """ merges only the modules from 'other' into 'self'""" self.ambiguous |= self.module_names & other.module_names + equipment_id = other['node']['equipment_id'] for name, mod in other.items(): if name == 'node': continue if name not in self.module_names: self.module_names.add(name) self[name] = mod + mod['original_id'] = equipment_id def process_file(filename, log): diff --git a/frappy/modulebase.py b/frappy/modulebase.py index d572dcd..b0154be 100644 --- a/frappy/modulebase.py +++ b/frappy/modulebase.py @@ -319,6 +319,8 @@ class Module(HasAccessibles): slowinterval = Property('poll interval for other parameters', FloatRange(0.1, 120), default=15) omit_unchanged_within = Property('default for minimum time between updates of unchanged values', NoneOr(FloatRange(0)), export=False, default=None) + original_id = Property('original equipment_id\n\ngiven only if different from equipment_id of node', + NoneOr(StringType()), default=None, export=True) # exported as custom property _original_id enablePoll = True pollInfo = None diff --git a/frappy/protocol/router.py b/frappy/protocol/router.py index 40f99f3..55d20ea 100644 --- a/frappy/protocol/router.py +++ b/frappy/protocol/router.py @@ -77,26 +77,30 @@ class SecopClient(frappy.client.SecopClient): class Router(frappy.protocol.dispatcher.Dispatcher): - singlenode = None - def __init__(self, name, logger, options, srv): """initialize router Use the option node = for a single node or nodes = ["", "" ...] for multiple nodes. - If a single node is given, the node properties are forwarded transparently, + If a single node is given, and no more additional modules are given, + the node properties are forwarded transparently, else the description property is a merge from all client node properties. """ uri = options.pop('node', None) uris = options.pop('nodes', None) - if uri and uris: - raise frappy.errors.ConfigError('can not specify node _and_ nodes') + try: + if uris is not None: + if isinstance(uris, str) or not all(isinstance(v, str) for v in uris) or uri: + raise TypeError() + elif isinstance(uri, str): + uris = [uri] + else: + raise TypeError() + except Exception as e: + raise frappy.errors.ConfigError("a router needs either 'node' as a string'" + "' or 'nodes' as a list of strings") from e super().__init__(name, logger, options, srv) - if uri: - self.nodes = [SecopClient(uri, logger.getChild('routed'), self)] - self.singlenode = self.nodes[0] - else: - self.nodes = [SecopClient(uri, logger.getChild(f'routed{i}'), self) for i, uri in enumerate(uris)] + self.nodes = [SecopClient(uri, logger.getChild(f'routed{i}'), self) for i, uri in enumerate(uris)] # register callbacks for node in self.nodes: node.register_callback(None, node.updateEvent, node.descriptiveDataChange, node.nodeStateChange) @@ -127,8 +131,8 @@ class Router(frappy.protocol.dispatcher.Dispatcher): logger.warning('can not connect to node %r', node.nodename) def handle_describe(self, conn, specifier, data): - if self.singlenode: - return DESCRIPTIONREPLY, specifier, self.singlenode.descriptive_data + if len(self.nodes) == 1 and not self.secnode.modules: + return DESCRIPTIONREPLY, specifier, self.nodes[0].descriptive_data reply = super().handle_describe(conn, specifier, data) result = reply[2] allmodules = result.get('modules', {}) @@ -144,6 +148,7 @@ class Router(frappy.protocol.dispatcher.Dispatcher): self.log.info('module %r is already present', modname) else: allmodules[modname] = moddesc + moddesc.setdefault('original_id', equipment_id) result['modules'] = allmodules result['description'] = '\n\n'.join(node_description) return DESCRIPTIONREPLY, specifier, result diff --git a/test/test_modules.py b/test/test_modules.py index 9b865cd..b9069ba 100644 --- a/test/test_modules.py +++ b/test/test_modules.py @@ -243,7 +243,7 @@ def test_ModuleMagic(): 'export', 'group', 'description', 'features', 'meaning', 'visibility', 'implementation', 'interface_classes', 'target', 'stop', 'status', 'param1', 'param2', 'cmd', 'a2', 'pollinterval', 'slowinterval', 'b2', - 'cmd2', 'value', 'a1', 'omit_unchanged_within'} + 'cmd2', 'value', 'a1', 'omit_unchanged_within', 'original_id'} assert set(cfg['value'].keys()) == { 'group', 'export', 'relative_resolution', 'visibility', 'unit', 'default', 'value', 'datatype', 'fmtstr',