Skip to content
Snippets Groups Projects
Commit 9e1c4a12 authored by David Hammer's avatar David Hammer
Browse files

Much better device scene

parent b1b330fd
No related branches found
No related tags found
2 merge requests!12Snapshot: field test deployed version as of end of run 202201,!3Base correction device, CalCat interaction, DSSC and AGIPD devices
......@@ -222,7 +222,7 @@ class BaseCorrection(PythonDevice):
.key("availableScenes")
.setSpecialDisplayType(DT_SCENES)
.readOnly()
.initialValue(["overview", "constants"])
.initialValue(["overview"])
.commit(),
)
......@@ -590,14 +590,8 @@ class BaseCorrection(PythonDevice):
payload["success"] = True
if scene_name == "overview":
payload["data"] = scenes.correction_device_overview_scene(
device_id=self.getInstanceId()
)
elif scene_name == "constants":
payload["data"] = scenes.correction_device_constants_scene(
device_id=self.getInstanceId(),
schema=self.getFullSchema(),
param_prefix="constantParameters",
status_prefix="foundConstants",
)
else:
payload["success"] = False
......
from karabo.bound import Types
import textwrap
import karathon
from karabo.common.scenemodel.api import (
ColorBoolModel,
DisplayCommandModel,
DisplayLabelModel,
DisplayStateColorModel,
DisplayTextLogModel,
DoubleLineEditModel,
EvaluatorModel,
IntLineEditModel,
LineEditModel,
LabelModel,
SceneModel,
write_scene,
RectangleModel,
)
BASE_INC = 30
PADDING = 10
RECONFIGURABLE = 4 # TODO: look up proper enum
_type_to_line_editable = {
"DOUBLE": (DoubleLineEditModel, {}),
"FLOAT": (DoubleLineEditModel, {}),
"INT32": (IntLineEditModel, {}),
"UINT32": (IntLineEditModel, {}),
"INT64": (IntLineEditModel, {}),
"UINT64": (IntLineEditModel, {}),
"STRING": (LineEditModel, {"klass": "EditableLineEdit"}),
}
def correction_device_constants_scene(device_id, schema, param_prefix, status_prefix):
# TODO: are there layout models in scene model somewhere? like gridbox?
subscenes = []
max_height = 0
# first column: parameters
row_offset = PADDING
col_offset = PADDING
subscenes.append(
LabelModel(
text="Parameters used for CalCat queries",
width=12 * BASE_INC,
x=col_offset,
y=row_offset,
)
)
row_offset += BASE_INC
for key in schema.getKeys(param_prefix):
key_path = f"{param_prefix}.{key}"
subscenes.append(
LabelModel(
text=schema.getDisplayedName(key_path)
if schema.hasDisplayedName(key_path)
else key,
width=5 * BASE_INC,
height=BASE_INC,
x=col_offset,
y=row_offset,
)
)
subscenes.append(
DisplayLabelModel(
keys=[f"{device_id}.{param_prefix}.{key}"],
width=7 * BASE_INC,
height=BASE_INC,
x=col_offset + 5 * BASE_INC,
y=row_offset,
def titled(title, width=8):
def actual_decorator(component_class):
# should this create subclass instead of mutating?
orig_render = component_class.render
def new_render(self, x, y, *args, **kwargs):
return [
LabelModel(
frame_width=1,
text=title,
width=width * BASE_INC,
height=BASE_INC,
x=x,
y=y,
)
] + orig_render(self, x, y + BASE_INC, *args, **kwargs)
component_class.render = new_render
orig_height = component_class.height
def new_height(self):
return orig_height(self) + BASE_INC
component_class.height = new_height
return component_class
return actual_decorator
def boxed(component_class):
# should this create subclass instead of mutating?
orig_render = component_class.render
orig_height = component_class.height
orig_width = component_class.width
def new_render(self, x, y, *args, **kwargs):
return [
RectangleModel(
x=x,
y=y,
width=orig_width(self) + 2 * PADDING,
height=orig_height(self) + 2 * PADDING,
stroke="#000000",
)
] + orig_render(self, x + PADDING, y + PADDING, *args, **kwargs)
component_class.render = new_render
def new_height(self):
return orig_height(self) + 2 * PADDING
component_class.height = new_height
def new_width(self):
return orig_width(self) + 2 * PADDING
component_class.width = new_width
return component_class
class MaybeEditableRow:
def __init__(
self,
device_id,
schema_hash,
key_path,
label_width=7,
display_width=5,
edit_width=5,
):
self.label_width = label_width * BASE_INC
self.display_width = display_width * BASE_INC
self.edit_width = edit_width * BASE_INC
key_attr = schema_hash.getAttributes(key_path)
label_text = textwrap.shorten(
key_attr["displayedName"]
if "displayedName" in key_attr
else key_path.split(".")[-1],
24,
)
if schema.getAccessMode(key_path) == RECONFIGURABLE:
value_type = schema.getValueType(key_path)
if value_type in (Types.DOUBLE, Types.FLOAT):
subscenes.append(
DoubleLineEditModel(
keys=[f"{device_id}.{param_prefix}.{key}"],
width=7 * BASE_INC,
height=BASE_INC,
x=col_offset + 12 * BASE_INC,
y=row_offset,
)
)
elif value_type in (Types.INT32, Types.UINT32, Types.INT64, Types.UINT64):
subscenes.append(
IntLineEditModel(
keys=[f"{device_id}.{param_prefix}.{key}"],
width=7 * BASE_INC,
height=BASE_INC,
x=col_offset + 12 * BASE_INC,
y=row_offset,
)
)
else:
subscenes.append(
LabelModel(
text=f"Not yet supported: editing {key} of type {value_type}",
width=7 * BASE_INC,
height=BASE_INC,
x=col_offset + 12 * BASE_INC,
y=row_offset,
)
)
row_offset += BASE_INC
max_height = max(max_height, row_offset)
# second column: constants as currently loaded
row_offset = PADDING
col_offset += 19 * BASE_INC # width of first column
col_offset += PADDING # and padding
subscenes.append(
DisplayCommandModel(
keys=[f"{device_id}.loadMostRecentConstants"],
x=col_offset,
y=row_offset,
width=10 * BASE_INC,
self.label = LabelModel(
text=label_text,
width=self.label_width,
height=BASE_INC,
)
)
row_offset += BASE_INC
subscenes.append(
LabelModel(
text="Constants found and loaded",
width=10 * BASE_INC,
self.value_display = DisplayLabelModel(
keys=[f"{device_id}.{key_path}"],
width=self.display_width,
height=BASE_INC,
x=col_offset,
y=row_offset,
)
)
row_offset += BASE_INC
for constant_name in schema.getKeys(status_prefix):
constant_path = f"{status_prefix}.{constant_name}"
subscenes.append(
LabelModel(
text=constant_name,
width=10 * BASE_INC,
height=BASE_INC,
x=col_offset,
y=row_offset,
)
)
row_offset += BASE_INC
for field in ("found", "createdAt"):
subscenes.append(
LabelModel(
text=field, # TODO: displayedName
width=5 * BASE_INC,
if key_attr["accessMode"] == RECONFIGURABLE:
value_type = key_attr["valueType"]
if value_type in _type_to_line_editable:
line_editable_class, extra_args = _type_to_line_editable[value_type]
self.value_edit = line_editable_class(
keys=[f"{device_id}.{key_path}"],
width=self.edit_width,
height=BASE_INC,
x=col_offset,
y=row_offset,
**extra_args,
)
)
subscenes.append(
DisplayLabelModel(
keys=[f"{device_id}.{constant_path}.{field}"],
width=7 * BASE_INC,
else:
self.value_edit = LabelModel(
text=f"Not yet supported: editing {key_path} of type {value_type}",
width=self.edit_width,
height=BASE_INC,
x=col_offset + 5 * BASE_INC,
y=row_offset,
)
def height(self):
return BASE_INC
def width(self):
return self.label_width + self.display_width + self.edit_width
def render(self, x=None, y=None):
self.label.x = x
self.label.y = y
self.value_display.x = x + self.label_width
self.value_display.y = y
if hasattr(self, "value_edit"):
self.value_edit.x = x + self.label_width + self.display_width
self.value_edit.y = y
return [
self.label,
self.value_display,
self.value_edit,
]
else:
return [
self.label,
self.value_display,
]
@titled("Parameters used for CalCat queries", width=10)
@boxed
class ConstantParameterColumn:
def __init__(self, device_id, schema_hash, prefix="constantParameters"):
self.device_id = device_id
self.rows = [
MaybeEditableRow(device_id, schema_hash, f"{prefix}.{key}")
for key in schema_hash.get(prefix).getKeys()
]
def render(self, x, y):
res = []
y_offset = 0
for row in self.rows:
res.extend(row.render(x=x, y=y + y_offset))
y_offset += row.height()
res.append(
DisplayCommandModel(
keys=[f"{self.device_id}.loadMostRecentConstants"],
x=x + 7 * BASE_INC,
y=y + y_offset,
width=10 * BASE_INC,
height=BASE_INC,
)
row_offset += BASE_INC
max_height = max(max_height, row_offset)
)
scene = SceneModel(
height=max_height + 2 * PADDING,
width=col_offset + 12 * BASE_INC + PADDING,
children=subscenes,
)
return write_scene(scene)
return res
def height(self):
return sum(row.height() for row in self.rows) + BASE_INC
def correction_device_overview_scene(device_id):
subscenes = []
row_offset = 0
# device class and name
subscenes.append(
LabelModel(
text="Correction device",
width=5 * BASE_INC,
def width(self):
return max(row.width() for row in self.rows)
class ConstantNode:
def __init__(self, device_id, constant_path):
self.title = LabelModel(
text=constant_path.split(".")[-1],
width=7 * BASE_INC,
height=BASE_INC,
x=0,
y=row_offset,
)
)
subscenes.append(
DisplayLabelModel(
self.ampel = ColorBoolModel(
height=BASE_INC, width=BASE_INC, keys=[f"{device_id}.{constant_path}.found"]
)
def render(self, x, y):
self.title.x = x
self.title.y = y
self.ampel.x = x + 7 * BASE_INC
self.ampel.y = y
y += BASE_INC
return [self.title, self.ampel]
def height(self):
return BASE_INC
def width(self):
return 8 * BASE_INC
@titled("Found constants", width=6)
@boxed
class FoundConstantsColumn:
def __init__(self, device_id, schema_hash, prefix="foundConstants"):
self.device_id = device_id
self.rows = [
ConstantNode(device_id, f"{prefix}.{constant_name}")
for constant_name in schema_hash.get(prefix).getKeys()
]
def render(self, x, y):
res = []
y_offset = 0
for row in self.rows:
res.extend(row.render(x, y + y_offset))
y_offset += row.height()
return res
def height(self):
return sum(row.height() for row in self.rows)
def width(self):
return max(row.width() for row in self.rows)
class ConstantLoadedAmpeln:
def __init__(self, device_id, schema_hash, prefix="foundConstants"):
self.keys = [
f"{device_id}.{prefix}.{key}.found"
for key in schema_hash.get(prefix).getKeys()
]
def render(self, x, y):
return [
ColorBoolModel(
x=x + i * BASE_INC,
y=y,
height=BASE_INC,
width=BASE_INC,
keys=[key],
)
for i, key in enumerate(self.keys)
]
def width(self):
return BASE_INC * len(self.keys)
def height(self):
return BASE_INC
@titled("Device status", width=6)
@boxed
class CorrectionDeviceStatus:
def __init__(self, device_id):
self.name = DisplayLabelModel(
keys=[f"{device_id}.deviceId"],
width=10 * BASE_INC,
width=14 * BASE_INC,
height=BASE_INC,
x=5 * BASE_INC,
y=row_offset,
)
)
row_offset += BASE_INC
# device state
subscenes.append(
DisplayStateColorModel(
self.state = DisplayStateColorModel(
show_string=True,
keys=[f"{device_id}.state"],
width=5 * BASE_INC,
width=7 * BASE_INC,
height=BASE_INC,
# parent_component="DisplayComponent",
x=0,
y=row_offset,
)
)
subscenes.append(
DisplayLabelModel(
keys=[f"{device_id}.state"],
width=5 * BASE_INC,
self.rate = EvaluatorModel(
expression="f'{x:.02f}'",
keys=[f"{device_id}.performance.rate"],
width=7 * BASE_INC,
height=BASE_INC,
x=0,
y=row_offset,
)
)
row_offset += BASE_INC
# log of status
subscenes.append(
DisplayTextLogModel(
self.processing_time = EvaluatorModel(
expression="f'{x:.02f}'",
keys=[f"{device_id}.performance.processingDuration"],
width=7 * BASE_INC,
height=BASE_INC,
)
self.tid = DisplayLabelModel(
keys=[f"{device_id}.trainId"],
width=7 * BASE_INC,
height=BASE_INC,
)
self.status_log = DisplayTextLogModel(
keys=[f"{device_id}.status"],
width=10 * BASE_INC,
height=10 * BASE_INC,
x=0,
y=row_offset,
width=14 * BASE_INC,
height=20 * BASE_INC,
)
def render(self, x, y):
self.name.x = x
self.name.y = y
y += BASE_INC
self.state.x = x
self.state.y = y
self.tid.x = x + 7 * BASE_INC
self.tid.y = y
y += BASE_INC
self.rate.x = x
self.rate.y = y
self.processing_time.x = x + 7 * BASE_INC
self.processing_time.y = y
y += BASE_INC
self.status_log.x = x
self.status_log.y = y
return [
self.name,
self.state,
self.rate,
self.processing_time,
self.tid,
self.status_log,
]
def width(self):
return 14 * BASE_INC
def height(self):
return 23 * BASE_INC
class CompactCorrectionDeviceOverview:
def __init__(self, device_id, schema_hash):
self.status = DisplayStateColorModel(
show_string=True,
keys=[f"{device_id}.state"],
width=6 * BASE_INC,
height=BASE_INC,
)
self.rate = EvaluatorModel(
expression="f'{x:.02f}'",
keys=[f"{device_id}.performance.rate"],
width=4 * BASE_INC,
height=BASE_INC,
)
self.tid = DisplayLabelModel(
keys=[f"{device_id}.trainId"],
width=4 * BASE_INC,
height=BASE_INC,
)
self.ampeln = ConstantLoadedAmpeln(device_id, schema_hash)
def render(self, x, y):
self.status.x = x
self.status.y = y
x += self.status.width
self.rate.x = x
self.rate.y = y
x += self.rate.width
self.tid.x = x
self.tid.y = y
x += self.tid.width
return [self.status, self.rate, self.tid] + self.ampeln.render(x, y)
def correction_device_overview_scene(device_id, schema):
if isinstance(schema, karathon.Schema):
schema_hash = schema.getParameterHash()
else:
schema_hash = schema.hash
status_overview = CorrectionDeviceStatus(device_id)
cpc = ConstantParameterColumn(device_id, schema_hash)
fcc = FoundConstantsColumn(device_id, schema_hash)
subscenes = []
x = PADDING
y = PADDING
subscenes.extend(status_overview.render(x, y))
x += status_overview.width() + BASE_INC
subscenes.extend(cpc.render(x, y))
x += cpc.width() + BASE_INC
subscenes.extend(fcc.render(x, y))
scene = SceneModel(
height=max(status_overview.height(), cpc.height()) + 2 * PADDING,
width=2 * PADDING
+ 2 * BASE_INC
+ status_overview.width()
+ cpc.width()
+ fcc.width(),
children=subscenes,
)
row_offset += BASE_INC * 10
scene = SceneModel(height=845.0, width=742.0, children=subscenes)
return write_scene(scene)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment