Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 55 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

A package for building Phoebus GUIs

Techui-builder is a module for building and organising phoebus gui screens using a builder-ibek yaml description of an IOC, with a user created techui.yaml file containing a description of the screens the user wants to create.
Techui-builder is a module for building and organising Phoebus CS-Studio `.bob` screens from a `techui.yaml` description of a beamline's IOC services. It also can auto-generate a synoptic `index.bob` showing devices laid out along the beam pipe and vacuum pipe, eliminating the need to hand-craft the overview screen in Phoebus.

The `.bob` file screens are intended to be served to [Daedalus](https://github.com/DiamondLightSource/daedalus), Diamond's web-based control system UI.

Source | <https://github.com/DiamondLightSource/techui-builder>
:---: | :---:
Expand All @@ -17,40 +19,70 @@ Releases | <https://github.com/DiamondLightSource/techui-builder/releases
The process to use this module goes as follows (WIP):

## Requirements

1. Docker
2. VSCode
3. CS-Studio (Phoebus)

## Installation
1. Clone this module with the `--recursive` flag to pull in [techui-support](git@github.com:DiamondLightSource/techui-support.git) for the associated bob files.
2. Open the project using VSCode.
3. Reopen the project in a container. Make sure you are using the VSCode extension: Dev Containers by Microsoft.

## Setting Up
## Setup

1. Clone this module with the `--recursive` flag to pull in [techui-support](https://github.com/DiamondLightSource/techui-support) for the associated bob file templates and SVG symbols.

2. Open the project using VSCode and reopen in the dev container when prompted. Make sure you are using the VSCode extension: Dev Containers by Microsoft.

3. Clone your beamline `ixx-services` repo to the root of this project, ensuring each IOC service has been converted to the ibek format.

1. Clone the beamline `ixx-services` repo to the root of this project, ensuring each IOC service has been converted to the [ibek](git@github.com:epics-containers/ibek.git) format.

`git clone --recursive git@gitlab.diamond.ac.uk:controls/containers/beamline/ixx-services.git`
1. Create your handmade synoptic overview screen in Phoebus and place at `ixx-services/synoptic/index.bob`.
## Writing a `techui.yaml` from scratch

1. Construct a `techui.yaml` file inside `ixx-services/synoptic` containing all the components from the services:

```
beamline:
short_dom: {e.g. b23, b01-1}
long_dom: {e.g. bl23b}
location: {e.g. ixx, ixx-1}
domain: {e.g. blxxi}
desc: {beamline description}
url: {e.g. b23-opis.diamond.ac.uk}
url: {e.g. ixx-opis.diamond.ac.uk}

components:
{component name}:
desc: {component description}
beam_pipe:
{component name, e.g. S1}:
label: {component description}
prefix: {PV prefix}
icon_type: {e.g. slits}
extras:
- {extra prefix 1}
- {extra prefix 2}

vacuum_pipe:
{component name, e.g. img01}:
label: {e.g. IMG 01}
prefix: {PV prefix}
icon_type: {e.g. img}

```
> [!NOTE]
> `extras` is optional, but allows any embedded screen to be added to make a summary screen e.g. combining all imgs, pirgs and ionps associated with a vacuum space.
> `extras` is optional, but allows any embedded screen to be added to a summary screen

Devices are rendered left-to-right in the order they appear in the file.

For devices not on the beam/vacuum pipe (e.g. detectors, sample environments etc), declare they under `components`. These generate `.bob` screens but do not appear on the synoptic overview.

> [!NOTE]
> If you already have a hand-crafted `index.bob` and do not define `beam_pipe` or `vacuum_pipe` in `techui.yaml`, it will be preserved as is. Defining either section will auto-generate and overwrite `index.bob`.

## Icon Type Naming Convention ##

`icon_type` values must use underscores. The corresponding SVG in `techui-support/symbols/` must use hyphens. For example:

| `icon_type` | SVG file
| ----------- | -----------
| `ion_pump` | `ion-pump.svg`
| `camera` | `camera.svg`

See the techui-support README for the full list of available symbols.

## Schema Generation ##

1. Run this command to locally generate a schema, which can be used for validation testing

```$ techui-builder schema```
Expand All @@ -63,15 +95,19 @@ The process to use this module goes as follows (WIP):

## Generating the Synoptic

`$ techui-builder build /path/to/synoptic/techui.yaml`
`$ techui-builder generate /path/to/synoptic/techui.yaml`

This populates `index.bob` and individual component screens inside `ixx-services/synoptic`.

Output files are written to `ixx-services/synoptic/`.

## Generating the JsonMap

`$ techui-builder generate-jsonmap /path/to/synoptic/index.bob`

This populates `JsonMap.json` with the tree of component screens inside `ixx-services/synoptic/index.bob`.
This populates `JsonMap.json` with the tree of component screens inside `ixx-services/synoptic/index.bob`. This is used for Daedalus navigation to create the tree view in the side panel.

Output files are written to `ixx-services/synoptic/`.

## Generating the Status PV database file

Expand Down
2 changes: 2 additions & 0 deletions src/techui_builder/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from techui_builder._version import __version__
from techui_builder.generate_jsonmap import app as generate_jsonmap_app
from techui_builder.main_app import app as main_app
from techui_builder.main_app import main as build_main
from techui_builder.schema_generator import app as schema_app
from techui_builder.status import app as status_app

Expand Down Expand Up @@ -60,6 +61,7 @@ def _(


app.add_typer(main_app)
app.command("generate", help="Run techui-builder for a given techui.yaml")(build_main)
app.add_typer(schema_app, name="schema")
app.add_typer(generate_jsonmap_app, name="generate-jsonmap")
app.add_typer(status_app, name="status")
Expand Down
107 changes: 65 additions & 42 deletions src/techui_builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,55 +178,78 @@ def _validate_screen(self, screen_name: str):
widget_group_name = widget_group.get_element_value("name")
self.validator.validate_bob(screen_name, widget_group_name, widgets)

def create_screens(self):
"""Create the screens for each component in techui.yaml"""
if len(self.entities) == 0:
logger_.critical(
"No ioc entities found. This [italic]normally[/italic]"
" suggests an issue with finding ixx-services."
)
exit()

# Loop over every component defined in techui.yaml and locate
# any extras defined
def _create_component_screens(self):
"""Create screens for components defined in techui.yaml"""
for component_name, component in self.conf.components.items():
screen_entities: list[Entity] = []

# ONLY IF there is a matching component and entity, generate a screen
if component.prefix in self.entities.keys():
# Populate child labels for any entities
# with the same prefix as the component
for entity in self.entities[component.prefix]:
entity.child_labels = component.child_labels

screen_entities.extend(self.entities[component.prefix])

if component.extras is not None:
# If component has any extras, add them to the entries to generate
for extra_p in component.extras:
if extra_p not in self.entities.keys():
logger_.error(
f"Extra prefix {extra_p} for {component_name} does not"
" exist."
)
continue
screen_entities.extend(self.entities[extra_p])
if component.prefix not in self.entities.keys():
logger_.warning(
f"{self.techui.name}: The prefix [bold]{component.prefix}[/bold] "
f"set in the component [bold]{component_name}[/bold] does not match"
" any P field in the ioc.yaml files in services"
)
continue

for entity in self.entities[component.prefix]:
entity.child_labels = component.child_labels

# This is used by both generate and validate,
# so called beforehand for tidyness
self.generator.build_widgets(component_name, screen_entities)
self.generator.build_groups(component_name, self.conf.components)
screen_entities.extend(self.entities[component.prefix])

screens_to_validate = list(self.validator.validate.keys())
if component.extras is not None:
for extra_p in component.extras:
if extra_p not in self.entities.keys():
logger_.error(
f"Extra prefix {extra_p} for {component_name} does not"
" exist."
)
continue
screen_entities.extend(self.entities[extra_p])

if component_name in screens_to_validate:
self._validate_screen(component_name)
else:
self._generate_screen(component_name)
self.generator.build_widgets(component_name, screen_entities)
self.generator.build_groups(component_name, self.conf.components)

if component_name in list(self.validator.validate.keys()):
self._validate_screen(component_name)
else:
self._generate_screen(component_name)

def _create_pipe_screens(self):
"""Create screens for beam_pipe and vacuum_pipe components"""
all_pipe_components = {
**(self.conf.beam_pipe or {}),
**(self.conf.vacuum_pipe or {}),
}

for component_name, pipe_component in all_pipe_components.items():
pv_root = pipe_component.prefix.split(":", maxsplit=1)[0]

if pv_root not in self.entities:
logger_.warning(
f"{self.techui.name}: The prefix [bold]{component.prefix}[/bold] "
f"set in the component [bold]{component_name}[/bold] does not match"
" any P field in the ioc.yaml files in services"
f"Pipe component '{component_name}' with prefix "
f"'{pipe_component.prefix}' does not match any entity "
f"in the ioc.yaml files — skipping screen generation."
)
continue

screen_entities = self.entities[pv_root]

self.generator.build_widgets(component_name, screen_entities)
self.generator.build_groups(component_name, {})

if component_name in list(self.validator.validate.keys()):
self._validate_screen(component_name)
else:
self._generate_screen(component_name)

def create_screens(self):
"""Create the screens for each component in techui.yaml"""
if len(self.entities) == 0:
logger_.critical(
"No ioc entities found. This [italic]normally[/italic]"
" suggests an issue with finding ixx-services."
)
return

self._create_component_screens()
self._create_pipe_screens()
Loading
Loading