diff --git a/schemas/pvi.device.schema.json b/schemas/pvi.device.schema.json
index 46deff7d..78bb1a33 100644
--- a/schemas/pvi.device.schema.json
+++ b/schemas/pvi.device.schema.json
@@ -67,6 +67,13 @@
"title": "Labels",
"type": "array"
},
+ "number_of_bits": {
+ "default": 8,
+ "description": "Number of bits to display",
+ "exclusiveMinimum": 0,
+ "title": "Number Of Bits",
+ "type": "integer"
+ },
"type": {
"const": "BitField",
"default": "BitField",
@@ -339,6 +346,26 @@
"additionalProperties": false,
"description": "Progress bar from lower to upper limit of a float PV",
"properties": {
+ "use_pv_limits": {
+ "default": false,
+ "description": "Use PV limits, if True then maximum and minumum are ignored",
+ "title": "Use Pv Limits",
+ "type": "boolean"
+ },
+ "minimum": {
+ "default": 0.0,
+ "description": "Lower limit of progress bar",
+ "exclusiveMinimum": 0.0,
+ "title": "Minimum",
+ "type": "number"
+ },
+ "maximum": {
+ "default": 1.0,
+ "description": "Upper limit of progress bar",
+ "exclusiveMinimum": 0.0,
+ "title": "Maximum",
+ "type": "number"
+ },
"type": {
"const": "ProgressBar",
"default": "ProgressBar",
diff --git a/src/pvi/_format/adl.py b/src/pvi/_format/adl.py
index 1fbbb26f..66c853de 100644
--- a/src/pvi/_format/adl.py
+++ b/src/pvi/_format/adl.py
@@ -54,7 +54,9 @@ def set(
case TextWrite(format=format) | TextRead(format=format) if (
is_text_widget(template) and format is not None
):
- template = add_property(template, "format", ADL_TEXT_FORMATS[format])
+ template = add_property(
+ template, "format", ADL_TEXT_FORMATS[TextFormat(format)]
+ )
return template
diff --git a/src/pvi/_format/bob.py b/src/pvi/_format/bob.py
index 5ea1b82b..e834360c 100644
--- a/src/pvi/_format/bob.py
+++ b/src/pvi/_format/bob.py
@@ -7,7 +7,10 @@
from pvi._format.widget import UITemplate, WidgetFormatter
from pvi.device import (
LED,
+ BitField,
ComboBox,
+ ImageRead,
+ ProgressBar,
TableRead,
TableWrite,
TextFormat,
@@ -48,6 +51,9 @@ def set(
properties["y"] = bounds.y
properties["width"] = bounds.w
properties["height"] = bounds.h
+ if isinstance(widget, BitField):
+ properties["width"] = widget.number_of_bits * 20
+ bounds.w = properties["width"]
widget_type = template.attrib.get("type", "")
@@ -81,7 +87,18 @@ def set(
"textupdate",
TextRead(format=format),
) if format is not None:
- add_format(t_copy, BOB_TEXT_FORMATS[format])
+ add_format(t_copy, BOB_TEXT_FORMATS[TextFormat(format)]) # type: ignore
+ case ("byte_monitor", BitField()):
+ add_byte_number_of_bits(t_copy, widget.number_of_bits) # type: ignore
+ case ("image", ImageRead()):
+ pass
+ case ("progressbar", ProgressBar()):
+ add_minimum_and_maximum_progressbar(
+ t_copy,
+ widget.use_pv_limits, # type: ignore
+ widget.minimum, # type: ignore
+ widget.maximum, # type: ignore
+ )
return t_copy
@@ -206,6 +223,18 @@ def add_format(element: ElementBase, format: str):
SubElement(element, "format").text = format
+def add_byte_number_of_bits(element: ElementBase, number_of_bits: int):
+ SubElement(element, "numBits").text = str(number_of_bits)
+
+
+def add_minimum_and_maximum_progressbar(
+ element: ElementBase, use_pv_limits: bool, minimum: float, maximum: float
+):
+ SubElement(element, "limits_from_pv").text = "true" if use_pv_limits else "false"
+ SubElement(element, "minimum").text = str(minimum)
+ SubElement(element, "maximum").text = str(maximum)
+
+
def find_element(root_element: ElementBase, tag: str, index: int = 0) -> ElementBase:
"""Iterate tree to find tag and replace text.
@@ -213,7 +242,6 @@ def find_element(root_element: ElementBase, tag: str, index: int = 0) -> Element
root_element: Root of tree to search
tag: Tag to search for in tree
index: Match to return if multiple matches are found
-
"""
match list(root_element.iter(tag=tag)):
case []:
diff --git a/src/pvi/_format/dls.bob b/src/pvi/_format/dls.bob
index 42bb7f3f..8f43ad9c 100644
--- a/src/pvi/_format/dls.bob
+++ b/src/pvi/_format/dls.bob
@@ -2,7 +2,7 @@
Display
12
- 12
+ 0
1000
800
4
@@ -192,9 +192,57 @@
Table
pva://Table
- 300
- 300
+ 671
+ 670
200
100
+
+
+ Column 1
+ 100
+ true
+
+
+
+
+ BitField
+ ByteMonitor
+ 172
+ 412
+ 165
+
+
+ ArrayTrace
+ 348
+ 412
+ 230
+ 230
+
+
+ $(traces[0].y_pv)
+
+ ArrayTrace
+
+ 0
+ 1
+
+
+
+
+ 1
+ 0
+ 0
+ 10
+ true
+
+
+
+
+ ImageRead
+ ImageRead
+ 566
+ 48
+ 300
+ 301
diff --git a/src/pvi/_format/dls.py b/src/pvi/_format/dls.py
index c283bbbc..97bcb502 100644
--- a/src/pvi/_format/dls.py
+++ b/src/pvi/_format/dls.py
@@ -228,6 +228,26 @@ def format_bob(self, device: Device, path: Path):
search="ProgressBar",
property_map={"pv_name": "pv"},
),
+ bitfield_formatter_cls=PVWidgetFormatter.from_template(
+ template,
+ search="BitField",
+ property_map={"pv_name": "pv"},
+ ),
+ button_panel_formatter_cls=PVWidgetFormatter.from_template(
+ template,
+ search="ButtonPanel",
+ property_map={"pv_name": "pv"},
+ ),
+ array_trace_formatter_cls=PVWidgetFormatter.from_template(
+ template,
+ search="ArrayTrace",
+ property_map={"y_pv": "pv"},
+ ),
+ image_read_formatter_cls=PVWidgetFormatter.from_template(
+ template,
+ search="ImageRead",
+ property_map={"pv_name": "pv"},
+ ),
text_read_formatter_cls=PVWidgetFormatter.from_template(
template,
search="TextUpdate",
diff --git a/src/pvi/_format/edl.py b/src/pvi/_format/edl.py
index dd2800df..5ba73af4 100644
--- a/src/pvi/_format/edl.py
+++ b/src/pvi/_format/edl.py
@@ -56,7 +56,7 @@ def set(
is_text_widget(template) and format is not None
):
template = add_property(
- template, "displayMode", EDL_TEXT_FORMATS[format]
+ template, "displayMode", EDL_TEXT_FORMATS[TextFormat(format)]
)
return template
diff --git a/src/pvi/_format/screen.py b/src/pvi/_format/screen.py
index ddba4196..70ed06ef 100644
--- a/src/pvi/_format/screen.py
+++ b/src/pvi/_format/screen.py
@@ -454,7 +454,7 @@ def generate_component_formatters(
for action, value in c.write_widget.actions.items()
]
# If this is a SignalRW, recreate the readback with a SignalR
- if isinstance(c, SignalRW):
+ if isinstance(c, SignalRW) and c.read_widget is not None:
row_components += [
SignalR(name=c.name, read_pv=c.read_pv, read_widget=c.read_widget)
]
diff --git a/src/pvi/_format/widget.py b/src/pvi/_format/widget.py
index 6e477402..7d7ce24e 100644
--- a/src/pvi/_format/widget.py
+++ b/src/pvi/_format/widget.py
@@ -7,9 +7,13 @@
from pvi._format.utils import Bounds, GroupType
from pvi.device import (
LED,
+ ArrayTrace,
+ BitField,
+ ButtonPanel,
CheckBox,
ComboBox,
Group,
+ ImageRead,
ProgressBar,
TableRead,
TableWrite,
@@ -260,14 +264,17 @@ class WidgetFormatterFactory(BaseModel, Generic[T]):
label_formatter_cls: Type[LabelWidgetFormatter[T]]
led_formatter_cls: Type[PVWidgetFormatter[T]]
progress_bar_formatter_cls: Type[PVWidgetFormatter[T]]
- # TODO: add bitfield, progress_bar, plot, image
text_read_formatter_cls: Type[PVWidgetFormatter[T]]
check_box_formatter_cls: Type[PVWidgetFormatter[T]]
combo_box_formatter_cls: Type[PVWidgetFormatter[T]]
text_write_formatter_cls: Type[PVWidgetFormatter[T]]
- table_formatter_cls: Type[PVWidgetFormatter]
+ table_formatter_cls: Type[PVWidgetFormatter[T]]
action_formatter_cls: Type[ActionWidgetFormatter[T]]
sub_screen_formatter_cls: Type[SubScreenWidgetFormatter[T]]
+ bitfield_formatter_cls: Optional[Type[PVWidgetFormatter[T]]] = None
+ array_trace_formatter_cls: Optional[Type[PVWidgetFormatter[T]]] = None
+ button_panel_formatter_cls: Optional[Type[PVWidgetFormatter[T]]] = None
+ image_read_formatter_cls: Optional[Type[PVWidgetFormatter[T]]] = None
def pv_widget_formatter(
self,
@@ -286,7 +293,7 @@ def pv_widget_formatter(
A WidgetFormatter representing the component
"""
- widget_formatter_classes: Dict[type, Type[PVWidgetFormatter[T]]] = {
+ widget_formatter_classes: Dict[type, Optional[Type[PVWidgetFormatter[T]]]] = {
# Currently supported formatters of ReadWidget/WriteWidget Components
LED: self.led_formatter_cls,
ProgressBar: self.progress_bar_formatter_cls,
@@ -296,11 +303,20 @@ def pv_widget_formatter(
ComboBox: self.combo_box_formatter_cls,
TextWrite: self.text_write_formatter_cls,
TableWrite: self.table_formatter_cls,
+ BitField: self.bitfield_formatter_cls,
+ ButtonPanel: self.button_panel_formatter_cls,
+ ArrayTrace: self.array_trace_formatter_cls,
+ ImageRead: self.image_read_formatter_cls,
}
if isinstance(widget, (TextRead, TextWrite)):
bounds.h *= widget.get_lines()
widget_formatter_cls = widget_formatter_classes[type(widget)]
+ if widget_formatter_cls is None:
+ raise RuntimeError(
+ f"Unsupported widget type {type(widget)} for this widget formatter"
+ )
+
return widget_formatter_cls(bounds=bounds, pv=pv, widget=widget)
diff --git a/src/pvi/device.py b/src/pvi/device.py
index 7ea6cc19..9228a5d2 100644
--- a/src/pvi/device.py
+++ b/src/pvi/device.py
@@ -2,7 +2,7 @@
import json
import re
-from enum import IntEnum
+from enum import Enum
from pathlib import Path
from typing import (
TYPE_CHECKING,
@@ -65,7 +65,7 @@ def enforce_pascal_case(s: str) -> str:
return s[0].upper() + s[1:]
-class TextFormat(IntEnum):
+class TextFormat(Enum):
"""Format to use for display of Text{Read,Write} widgets on a UI"""
decimal = 0
@@ -95,11 +95,21 @@ class BitField(ReadWidget):
"""LED and label for each bit of an int PV"""
labels: Sequence[str] = Field(description="Label for each bit")
+ number_of_bits: int = Field(
+ default=8, description="Number of bits to display", gt=0
+ )
class ProgressBar(ReadWidget):
"""Progress bar from lower to upper limit of a float PV"""
+ use_pv_limits: bool = Field(
+ default=False,
+ description="Use PV limits, if True then maximum and minumum are ignored",
+ )
+ minimum: float = Field(default=0.0, description="Lower limit of progress bar", gt=0)
+ maximum: float = Field(default=1.0, description="Upper limit of progress bar", gt=0)
+
class TextRead(ReadWidget):
"""Text view of any PV"""
diff --git a/tests/format/output/button.bob b/tests/format/output/button.bob
index a224fbbd..96fb1193 100644
--- a/tests/format/output/button.bob
+++ b/tests/format/output/button.bob
@@ -1,7 +1,7 @@
Display
0
- 0
+ 0
274
102
4
diff --git a/tests/format/output/combo_box.bob b/tests/format/output/combo_box.bob
index 11475a00..707f5188 100644
--- a/tests/format/output/combo_box.bob
+++ b/tests/format/output/combo_box.bob
@@ -1,7 +1,7 @@
Display
0
- 0
+ 0
274
54
4
diff --git a/tests/format/output/device_ref.bob b/tests/format/output/device_ref.bob
index cca5f681..e05cfdab 100644
--- a/tests/format/output/device_ref.bob
+++ b/tests/format/output/device_ref.bob
@@ -1,7 +1,7 @@
Display
0
- 0
+ 0
274
54
4
diff --git a/tests/format/output/index.bob b/tests/format/output/index.bob
index 566a94a5..c5daf3c6 100644
--- a/tests/format/output/index.bob
+++ b/tests/format/output/index.bob
@@ -1,7 +1,7 @@
Display
0
- 0
+ 0
388
105
4
diff --git a/tests/format/output/mixedWidgets.bob b/tests/format/output/mixedWidgets.bob
index bfe1bc39..ee45057c 100644
--- a/tests/format/output/mixedWidgets.bob
+++ b/tests/format/output/mixedWidgets.bob
@@ -1,7 +1,7 @@
Display
0
- 0
+ 0
576
688
4
@@ -1026,6 +1026,9 @@
144
124
20
+ false
+ 0.0
+ 1.0
diff --git a/tests/format/output/pva_table.bob b/tests/format/output/pva_table.bob
index d29e523e..1dfe7317 100644
--- a/tests/format/output/pva_table.bob
+++ b/tests/format/output/pva_table.bob
@@ -1,7 +1,7 @@
Display
0
- 0
+ 0
226
234
4
@@ -32,6 +32,13 @@
30
200
200
+
+
+ Column 1
+ 100
+ true
+
+
Column 1
diff --git a/tests/format/output/pva_table_panda.bob b/tests/format/output/pva_table_panda.bob
index 28b297e6..380b1132 100644
--- a/tests/format/output/pva_table_panda.bob
+++ b/tests/format/output/pva_table_panda.bob
@@ -1,7 +1,7 @@
Display
0
- 0
+ 0
1726
234
4
@@ -32,6 +32,13 @@
30
1700
200
+
+
+ Column 1
+ 100
+ true
+
+
Column 1
diff --git a/tests/format/output/static_table.bob b/tests/format/output/static_table.bob
index 49713e8a..0b66042f 100644
--- a/tests/format/output/static_table.bob
+++ b/tests/format/output/static_table.bob
@@ -1,7 +1,7 @@
Display
0
- 0
+ 0
274
78
4
diff --git a/tests/format/output/static_table_BigTable.bob b/tests/format/output/static_table_BigTable.bob
index d2edb690..792f1cea 100644
--- a/tests/format/output/static_table_BigTable.bob
+++ b/tests/format/output/static_table_BigTable.bob
@@ -1,7 +1,7 @@
Display
0
- 0
+ 0
1030
150
4
diff --git a/tests/format/output/sub_screen.bob b/tests/format/output/sub_screen.bob
index c5f1ebfa..080d3c20 100644
--- a/tests/format/output/sub_screen.bob
+++ b/tests/format/output/sub_screen.bob
@@ -1,7 +1,7 @@
Display
0
- 0
+ 0
290
160
4
diff --git a/tests/format/output/sub_screen_Group1.bob b/tests/format/output/sub_screen_Group1.bob
index 4bbad837..78af2914 100644
--- a/tests/format/output/sub_screen_Group1.bob
+++ b/tests/format/output/sub_screen_Group1.bob
@@ -1,7 +1,7 @@
Display
0
- 0
+ 0
290
136
4
diff --git a/tests/format/output/sub_screen_Group4.bob b/tests/format/output/sub_screen_Group4.bob
index e8736cf2..c13d2f21 100644
--- a/tests/format/output/sub_screen_Group4.bob
+++ b/tests/format/output/sub_screen_Group4.bob
@@ -1,7 +1,7 @@
Display
0
- 0
+ 0
290
136
4
diff --git a/tests/format/output/text_format.bob b/tests/format/output/text_format.bob
index 84dca35d..b8cbc6c7 100644
--- a/tests/format/output/text_format.bob
+++ b/tests/format/output/text_format.bob
@@ -1,7 +1,7 @@
Display
0
- 0
+ 0
274
150
4