Files
seweb/secop.py
Markus Zolliker 62823899bb change update mechanism
Atoring just updates on the SecopInteractor does not work
properly, as these updates depend on what the clients have already.
As updates are polled internall anyway, store onm each client
the state of the parameters it has looked up already and emit
changes values only.
2026-03-20 11:49:00 +01:00

165 lines
6.6 KiB
Python

import logging
from frappy.client import SecopClient
def convert_par(module, name, par):
result = dict(type='input', name=module+":"+name, title=name)
if par.get('readonly', True):
result['type'] = 'rdonly'
else:
result['command'] = 'change %s:%s' % (module, name)
if par['datainfo']['type'] == 'enum':
result['enum_names'] = [dict(title=k, value=v) for k, v in par['datainfo']['members'].items()]
result['type'] = 'enum'
elif par['datainfo']['type'] == 'bool':
result['type'] = 'checkbox'
if par['description']:
result['info'] = par['description']
return result
def convert_cmd(module, name, cmd):
result = dict(type='pushbutton', name=module+":"+name, title=name)
result['command'] = 'do %s:%s' % (module, name)
argument = cmd['datainfo'].get('argument')
if cmd['datainfo'].get('result'):
result['result'] = True
else:
if not argument: # simple command like stop
return result
result['button'] = not argument
# result['type'] = pushbutton will be replaced below
if argument:
if argument['type'] == 'enum':
result['enum_names'] = [dict(title=k, value=v) for k, v in argument['members'].items()]
result['type'] = 'enum'
elif argument['type'] == 'bool':
result['type'] = 'checkbox'
else:
result['type'] = 'input'
else:
result['type'] = 'rdonly'
if cmd['description']:
result['info'] = cmd['description']
return result
class SecopInteractor(SecopClient):
prio_par = ["value", "status", "target"]
hide_par = ["baseclass", "class", "pollinterval"]
skip_par = ["status2"]
main_params = {'value', 'status', 'param'}
def __init__(self, uri, node_map, change_callback=None):
super().__init__(uri)
self.change_callback = change_callback
try:
self.connect()
self.update_node_map(node_map)
self.register_callback(None, descriptiveDataChange=self.descChanged)
except Exception as e:
print(repr(e))
def update_node_map(self, node_map):
node_map.update({k: self for k in self.modules})
def descChanged(self, module, desc):
if module is None and self.change_callback:
self.change_callback()
def add_main_components(self, components):
for name, desc in self.modules.items():
parameters = desc['parameters']
component = {'type': 'rdonly' if 'value' in parameters else 'none'}
if 'status' in parameters:
component['statusname'] = f'{name}:status'
targetpar = parameters.get('target')
if targetpar:
component.update(convert_par(name, 'target', targetpar))
component['targetname'] = f'{name}:target'
info = desc['properties'].get('description')
if info:
component['info'] = info
component['name'] = f'{name}:value'
component['title'] = name
components.append(component)
def get_components(self, path):
module = self.modules[path]
parameters = dict(module["parameters"])
components = []
for name in SecopInteractor.skip_par:
if name in parameters:
parameters.pop(name)
for name in SecopInteractor.prio_par:
if name in parameters:
components.append(convert_par(path, name, parameters.pop(name)))
components1 = []
for name in SecopInteractor.hide_par:
if name in parameters:
components1.append(convert_par(path, name, parameters.pop(name)))
for name, par in parameters.items():
components.append(convert_par(path, name, par))
components.extend(components1)
for name, cmd in module.get("commands", {}).items():
components.append(convert_cmd(path, name, cmd))
return components
def buildUpdateEvent(self, module, parameter, entry):
name = f'{module}:{parameter}'
if entry.readerror:
item = {'name': name, 'error': str(entry.readerror)}
elif parameter == 'status':
# statuscode: 0: DISABLED, 1: IDLE, 2: WARN, 3: BUSY, 4: ERROR
statuscode, statustext = entry[0]
formatted = statuscode.name + (f', {statustext}' if statustext else '')
item = {'name': name, 'value': str(entry), 'statuscode': entry[0][0] // 100,
'formatted': formatted}
else:
item = {'name': name, 'value': str(entry), 'formatted': entry.formatted()}
return item
def handle_command(self, command):
"""handle command if we can, else return False"""
if not command.strip():
return dict(type='accept-command')
is_param = True
if command.startswith('change '):
command = command[7:]
elif command.startswith('do '):
is_param = False
command = command[3:]
modpar, _, strvalue = command.partition(' ')
module, _, parameter = modpar.partition(':')
if not parameter:
parameter = 'target'
if module not in self.modules:
return None
logging.info('SENDCOMMAND %r', command)
if is_param:
try:
entry = self.setParameterFromString(module, parameter, strvalue)
item = {'name': f'{module}:{parameter}', 'value': str(entry), 'formatted': entry.formatted()}
except Exception as e:
print(f"{e!r} converting {strvalue} to {self.modules[module]['parameters'][parameter]['datatype']}")
self.updates[module, parameter] = item
return True
# called a command
item = self.execCommandFromString(module, parameter, strvalue)[0] # ignore qualifiers
return {'name': f'{module}:{parameter}', 'value': str(item.value), 'formatted': item.formatted()}
def get_updates(self, client_state, update_filter):
updates = []
main_params = self.main_params if '' in update_filter else ()
for (module, param), item in self.cache.items():
if param in main_params or module in update_filter:
key = module, param
prev = client_state.get(key)
if prev is None or item.value != prev.value or item.readerror != prev.readerror:
updates.append(self.buildUpdateEvent(module, param, item))
client_state[key] = item
return updates
def info(self):
return ["na"]