bobview
A lean viewer for Phoebus Display Builder
.bob screens, written in Rust with egui.
It also opens the older CS-Studio BOY .opi screens.
It shows live EPICS Channel Access and PV Access values, including areaDetector
images, and is small enough to run on a phone, in the Android 16 Linux
Terminal (Debian 13, aarch64). The same app runs in a browser, served by
bobview-server.
Trends plot PVs' archived history from an EPICS Archiver Appliance as well as their live values. Scripts (Jython/JavaScript), display editing and alarm services are out of scope; see Scope and fidelity to Phoebus.
New to bobview? Install it, start the simulated beamline and open your first live display.
🧭 How-to guides goal-orientedPractical steps: connect to real IOCs, run over ssh, open BOY screens, save layouts, deploy the web server.
💡 Explanations understanding-orientedBackground and design: what bobview aims to do, how it is built, how its windows and writes behave.
📖 Reference information-orientedCommand-line options, supported widgets, macros and rules, browser parameters and the Rust API.
The Rust API docs for the workspace's crates are published alongside this book.
The docs follow the Diátaxis framework: four kinds of page for four needs, learning, doing, understanding and looking things up.
Quick start
This tutorial installs bobview, starts a simulated beamline on your own
machine and walks you around its displays. It takes about ten minutes and
needs no EPICS installation and no IOCs: bobview-testioc plays the part of
the epics-containers example-services
BL01T beamline.
You need an x86_64 or aarch64 Linux machine (an Android phone's Linux Terminal works), or a Mac. To build it with Cargo instead, see Install from crates.io.
1. Install
On Debian or Ubuntu, first install the graphics libraries:
sudo apt install libxkbcommon0 libwayland-client0 libegl1 libgl1-mesa-dri
(on RHEL and Fedora see Install bobview; macOS needs
nothing). Then install bobview into ~/.local:
curl -sSfL https://raw.githubusercontent.com/gilesknap/bobview/main/install.sh | bash
Check that it is on your PATH:
bobview --version
If the command is not found, add export PATH=$HOME/.local/bin:$PATH to your
shell's start-up file and open a new terminal.
2. Start the simulated beamline
bobview-testioc &
This serves, over Channel Access and PV Access, a calculator IOC
(bl01t:A, bl01t:B, bl01t:SUM), two motors and a simulated area
detector that streams an image.
3. Open the displays
Tell the Channel Access client to look for PVs on this machine, and start the viewer:
EPICS_CA_ADDR_LIST=127.0.0.1 bobview
With no display named, bobview opens index.bob from the test screens it
installed. You should see live values within a second or two.
4. Look around
- Hover over a widget: a tooltip shows its PV and value.
- Right-click a widget (long-press on a touch screen): copy the PV name
or value, open the PV's details, or Trend it. A trend window plots the
PV against time; leave it open. Given an archiver (
--archiver URL), it shows the PV's history too: see Trend PVs. - Middle-click a widget to copy its PV name. On Linux a middle-click in a terminal pastes it; elsewhere, paste it as usual (Ctrl+V or ⌘V).
- Click a button that opens another display. Use Back and
Forward in the toolbar to move between them, and Home to return
to
index.bob. - Click the zoom in the toolbar to see the display at 100%, then Fit to fit it to the window again. On a touch screen, pinch to zoom.
- Open the navigator from the toolbar to see a tree of every display reachable from the home display.
5. Look at the camera
From the home display, open the camera display. The image comes over PV
Access from BL01T-DI-CAM-01:PVA:OUTPUT: drifting fringes with three spots
moving across them. Use the image toolbar to zoom, pan and place a crosshair
that reads out the pixel under it.
6. Change a value
From the home display, open the ea-test display. Type a new number into
the text entry for bl01t:A and press Enter, or drag its slider.
bl01t:SUM follows, as the calculator record adds A and B.
7. Finish
Close the window to quit bobview, then stop the IOC:
kill %1
Next steps
- Point bobview at your own IOCs: Connect to real IOCs.
- Open your own screens:
bobview path/to/screen.bob -m P=BL01T,R=:DET:. - Show the same displays in a web browser: Serve displays to a browser.
Serve displays to a browser
bobview also builds for the browser (WebAssembly, drawn with WebGL).
bobview-server serves the web build, your .bob files and a
PV Web Socket (PVWS) from one port. In
this tutorial you serve the simulated beamline to a browser on your own
machine.
Do the Quick start first, so that bobview and
bobview-testioc are installed.
1. Start the IOC and the server
bobview-testioc &
EPICS_CA_ADDR_LIST=127.0.0.1 bobview-server --opi ~/.local/share/bobview/displays &
The server reaches PVs with bobview's own Channel Access and PV Access
client, so it needs the same EPICS_* variables as the viewer. It listens
on 127.0.0.1:8080, so only this machine can reach it.
2. Open the page
Browse to http://localhost:8080/?display=index.bob.
The same displays appear as in the desktop viewer, drawn in the page. Click through them as before. In a browser displays have no windows of their own: every link navigates in place.
3. Try page parameters
Add macros and a fixed zoom to the address:
http://localhost:8080/?display=demo.bob¯os=P=BL01T&zoom=1.5
All the parameters are listed in Browser page parameters.
4. Allow writes
Writes are off unless the server allows them, since they go out as the
server's user. Stop the server and start it again with --write:
kill %2
EPICS_CA_ADDR_LIST=127.0.0.1 bobview-server --opi ~/.local/share/bobview/displays --write
It prints a random write token to the terminal. Open the page with it:
http://localhost:8080/?display=index.bob&token=TOKEN
The page removes the token from its address once it has read it, so a reload needs the token again.
Next steps
To let other machines use the server, read Deploy bobview-server for others before binding it to a public address.
Install from crates.io
This tutorial builds bobview from crates.io with Cargo, instead of downloading a release, and opens the test displays against the simulated beamline. Use it on a machine the release builds don't cover, or to build with your own Rust toolchain. It takes about fifteen minutes, most of it compiling.
1. Install Rust
Install Rust with rustup if you don't have it:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
and open a new terminal, so that ~/.cargo/bin is on your PATH. The build
also needs a C compiler: sudo apt install build-essential on Debian and
Ubuntu, sudo dnf install gcc on RHEL and Fedora, or xcode-select --install
on macOS.
2. Install the graphics libraries
bobview needs these at run time, as the release does. On Debian or Ubuntu:
sudo apt install libxkbcommon0 libwayland-client0 libegl1 libgl1-mesa-dri
On RHEL, Rocky, Alma and Fedora:
sudo dnf install libxkbcommon libwayland-client libglvnd-egl mesa-dri-drivers
macOS needs nothing.
3. Install bobview and the test IOC
cargo install bobview bobview-testioc
This builds and installs into ~/.cargo/bin:
| command | what |
|---|---|
bobview | the viewer |
bobview-probe | prints live PV values from the command line |
bobview-testioc | a pure-Rust IOC serving the example-services BL01T PVs |
Check it works:
bobview --version
4. Start the simulated beamline
bobview-testioc &
5. Open the test displays
Cargo installs only the programs, not the test displays that a release installs. bobview opens displays over https, so open them from GitHub:
EPICS_CA_ADDR_LIST=127.0.0.1 bobview https://raw.githubusercontent.com/gilesknap/bobview/main/tests/displays/index.bob
Their links to other displays and images are fetched from the same place. You should see live values within a second or two. From here, carry on from step 4 of the Quick start.
Upgrade or remove
Run cargo install bobview bobview-testioc again to upgrade to the latest
version, and cargo uninstall bobview bobview-testioc to remove them.
What Cargo does not install
bobview-ssh, the wrapper forssh -X(see Choose bobview or bobview-ssh). It only sets three environment variables, so you can set them yourself or copy the script next tobobview.- The browser build.
cargo install bobview-serverinstalls the server, but it needs the web build from a release or from Build the browser version, given with--web DIR. The browser tutorial uses the release.
Install bobview
bobview runs on x86_64 and aarch64 Linux (including the Android 16 Linux
Terminal, Debian 13), on macOS 11 or later (Apple silicon or Intel), and on
x86_64 Windows (see below). install.sh picks the release build
to match the machine. To build it from crates.io with Cargo instead, see
Install from crates.io.
Install the prerequisites
On Debian and Ubuntu:
sudo apt install libxkbcommon0 libwayland-client0 libegl1 libgl1-mesa-dri
On RHEL, Rocky, Alma and Fedora:
sudo dnf install libxkbcommon libwayland-client libglvnd-egl mesa-dri-drivers
None are needed on macOS.
Install the release
curl -sSfL https://raw.githubusercontent.com/gilesknap/bobview/main/install.sh | bash
This installs into ~/.local:
| file | what |
|---|---|
bin/bobview | the viewer |
bin/bobview-ssh | runs the viewer with software OpenGL, for ssh -X and machines whose GPU can't be used; see Choose bobview or bobview-ssh |
bin/bobview-testioc | a pure-Rust IOC serving the example-services BL01T PVs |
bin/bobview-probe | prints live PV values from the command line |
bin/bobview-server | serves bobview to browsers, see Deploy bobview-server |
share/bobview/displays/ | test screens; index.bob opens by default |
share/bobview/web/ | the browser build that bobview-server serves |
The Linux binaries are linked against glibc 2.28, so they run on RHEL 8+, Ubuntu 20.04+ and Debian 10+.
Put it on your PATH
On macOS ~/.local/bin is not on PATH by default; add this to ~/.zshrc:
export PATH=$HOME/.local/bin:$PATH
Upgrade or remove
Run the install command again to upgrade; it replaces the binaries and the
displays and web folders. To remove bobview, delete the bin/bobview*
files and share/bobview from ~/.local.
Windows
The Windows build is new, and has not yet been tried against real IOCs.
Download bobview-x86_64-windows.zip from the
latest release, unzip
it anywhere, and run bin\bobview.exe. Nothing else needs installing. The
zip holds the same files as the table above, as .exe, without bobview-ssh
and install.sh; add its bin folder to PATH to run the commands from a
terminal. To try it against the simulated beamline, in PowerShell:
Start-Process bin\bobview-testioc.exe
$env:EPICS_CA_ADDR_LIST = "127.0.0.1"
bin\bobview.exe
Windows may ask to let bobview-testioc and bobview through the firewall:
Channel Access and PV Access search for PVs over UDP.
bobview draws with OpenGL, which a GPU driver provides. A virtual machine or
remote desktop session without one has only Windows' built-in OpenGL 1.1,
which is too old, and bobview fails to start. There, put Mesa's software
opengl32.dll (llvmpipe, for example from
mesa-dist-win) beside
bobview.exe, and it draws on the CPU instead.
Layouts are saved in %APPDATA%\bobview\layouts (in
%XDG_CONFIG_HOME%\bobview\layouts if that is set). DLS launcher scripts,
which are shell scripts, are not run on Windows, so their command actions do
nothing (see How BOY screens are translated).
To remove bobview, delete the unzipped folder and %APPDATA%\bobview.
Connect to real IOCs
bobview uses the standard EPICS client variables (see Environment variables).
Search by UDP
Name the hosts (or broadcast addresses) to search:
EPICS_CA_ADDR_LIST="ioc1.example.com 10.0.0.255" bobview screen.bob
EPICS_PVA_ADDR_LIST=10.0.0.255 bobview screen.bob
Go through a gateway over TCP
When UDP search can't reach the IOCs, for example from a phone's Linux Terminal VM behind NAT, name a gateway reached over TCP. This works through NAT since the phone opens the connection:
EPICS_CA_NAME_SERVERS=gateway.example.com:9064 bobview screen.bob
EPICS_PVA_NAME_SERVERS=gateway.example.com:5075 bobview screen.bob
The example-services ca-gateway listens on port 9064 of its host.
Choose the protocol
PV names prefixed pva:// use PV Access; those with no prefix, or ca://,
use Channel Access. The other prefixes are in
Macros, rules and PV types.
Check a PV without the viewer
bobview-probe prints live values from the command line, and can write:
bobview-probe bl01t:SUM pva://bl01t:A
bobview-probe --seconds 10 --put bl01t:A=3.5 bl01t:SUM
Watch only, never write
Start the viewer with --read-only to refuse every write (see
Writes and safety):
bobview --read-only screen.bob
For more output while diagnosing a connection, set BOBVIEW_LOG=debug.
Open displays over http(s)
A display can be a file or an http:// / https:// URL, on the command line
or typed into Open…:
bobview http://host:8080/opi/demo.bob
Files that a display names (buttons, embedded displays, navigation tabs) are found relative to it, so a directory of screens served by any web server works as it does from disk. For example, to serve a folder of screens:
cd /path/to/screens && python3 -m http.server 8000
bobview http://localhost:8000/index.bob
Fetched screens are kept in memory for Back and Forward; Reload fetches them again.
A display loaded over http(s) writes PVs as a local file does, but no command is ever run for it. See Writes and safety.
Choose bobview or bobview-ssh
bobview draws with OpenGL. Two commands start it:
| command | draws with | use it |
|---|---|---|
bobview | the machine's own OpenGL: the GPU, or whatever driver the desktop provides | normally: on a local desktop, a laptop, a Mac, the phone |
bobview-ssh | Mesa in software, on the CPU of the machine bobview runs on | when the OpenGL path can't work, such as over ssh -X |
Both take the same arguments. Start with bobview, and switch to
bobview-ssh if it fails to start or draws nothing.
When to use bobview-ssh
- Over
ssh -Xor-Y. This is what it was named for. Forwarded OpenGL reaches your local X server as indirect GLX, which current X servers refuse, sobobviewexits withglutin error: [2] BadValue. - Where a GPU driver is installed but can't be used, for example a remote desktop, VNC or Xvfb session on a machine with an NVIDIA driver: glvnd picks NVIDIA's GL, which then has no GPU display to draw on.
- Where the GPU driver is broken or misbehaves, as a workaround while you sort it out.
There is no need for it on macOS, on Wayland or X11 desktops with working
graphics, in the Android Linux Terminal, or for --snapshot, unless one of
the cases above applies.
What it does
bobview-ssh is a small script that runs bobview with:
LIBGL_ALWAYS_SOFTWARE=1, so Mesa renders on the CPU (llvmpipe);__GLX_VENDOR_LIBRARY_NAME=mesa, and__EGL_VENDOR_LIBRARY_FILENAMESpointing at Mesa's EGL vendor file, so libglvnd picks Mesa rather than a GPU vendor's library such as NVIDIA's.
Over X forwarding the finished window then travels to your X server as plain images, which every X server accepts.
Software rendering costs CPU on the machine running bobview, but bobview's
displays are simple to draw, so it is usually quick enough. Over ssh -X
each redraw sends the whole window over the network, so bobview redraws a
window only for its own input and when a PV it shows changes, at most 10
times a second (see Pinned windows and the viewer).
Requirement
Mesa must be installed where bobview runs: mesa-dri-drivers on RHEL,
libgl1-mesa-dri on Debian and Ubuntu (both are in the
install prerequisites).
Example
ssh -X control-host
bobview-ssh /path/to/screen.bob -m P=BL01T
Layouts restore window positions over X forwarding; see Save and restore a layout.
Open BOY screens from an Eclipse project or DLS module
Any .opi file opens like a .bob:
bobview screen.opi
BOY screens often name each other through the CS-Studio workspace, though:
an Eclipse project whose .project links folder names (mks937a, dlsPLC …)
to the directories that hold each module's screens, so ../mks937a/gauge.opi
means the mks937a link. This page shows how to open such screens so that
their links work. How BOY screens are translated
explains the rules behind it.
Through an Eclipse project
Open the screen through its project path:
bobview /workspaces/VacOPIS/vacuum_4-100/vacuum/diamondVacuumOverview.opi
for a project in /workspaces/VacOPIS/vacuum_4-100. bobview follows the
project's links (the linked folders need not exist on disk; Open… lists
them).
Or give the screen's real path and name the project with --project:
bobview -p /workspaces/VacOPIS/vacuum_4-100 /dls_sw/…/vacuumApp/opi/opi/diamondVacuumOverview.opi
A DLS module
DLS modules need neither. There is no .project on disk, but the generated
launcher runcss.sh beside a module's screens (vacuumApp/opi/opi) lists
the links it gives CS-Studio. bobview reads that list (it does not run the
script), so just open the screen by its real path:
bobview /dls_sw/…/vacuum/4-100/vacuumApp/opi/opi/diamondVacuumOverview.opi
This works for a BOY screen reached by its real path from anywhere (the file chooser, a layout, an absolute path in another screen), with the launcher in its folder or up to two above.
To open another module's BOY screen through this module's links, give -p
the module's screens folder (or its runcss.sh).
A screen that needs launcher macros
DLS screens are often opened by a launcher script, such as
./srCellVacuum 04, that supplies macros. For an .opi given on the command
line in an Eclipse project (through its project path, with --project, or
by its real path beside a DLS runcss.sh), bobview tries the launcher
scripts in its directory to find them: the one named <screen>.sh first,
with no arguments and with each number in the file name, bare and with an
r. So:
| screen | macros from |
|---|---|
linacVacuum.opi | linacVacuum.sh (dom=LI) |
srCell05Vacuum.opi | srCellVacuum 05 |
SR05C_rack.opi | srCellVacuum 05r |
Macros given with -m override these. Screens with no number in their name
(srInjCellVacuum.opi is cell 01) need -m with the macros their
launcher would give, or opening from the overview. Launcher scripts are not
run on Windows, so there every such screen needs -m.
Save and restore a layout
A layout records the open windows so that a control-room arrangement can be put back with one command. Pinned windows and the viewer explains how windows are opened in the first place.
Save
Arrange the windows, then use the main toolbar's ☰ menu:
- Save layout as… names a new layout (asking before replacing one).
- Save layout saves to the layout last loaded or saved.
- Save as default makes it the layout bobview starts with.
- Save default on exit (off unless ticked) saves the default each time you quit.
- Delete layout removes one.
Restore
Pick a layout from the toolbar's layout list; this closes the display and trend windows open now. From the command line:
bobview # restores the default layout if there is one
bobview DISPLAY # opens DISPLAY; the default layout is not restored
bobview --layout rf-shift # restores layouts/rf-shift.json
bobview --layout ~/shift.json # restores the layout in that file
bobview --no-layout # ignores the default layout
A --layout that has a directory in it or ends in .json is a layout
file's path, so a layout kept elsewhere (under version control, say) can be
restored without copying it into layouts. The toolbar shows it by its file
name, and Save layout writes back to that file.
--snapshot ignores layouts.
What a layout holds
For each window: its display, Eclipse project and effective macros, its position and size, whether it is pinned, and whether a display window shows its toolbar; for the main window also its zoom and whether it shows the navigator. For each trend window: its PVs (with their colours, value axes and whether they are hidden), its time range (the last so long, live, or a fixed window), its value axes' ranges, log scales and names, its position and size. The archiver is not saved in a layout: trends use bobview's (see Trend PVs). A layout from before trends showed archived history restores each trend's PVs, live over its time span, with its log scale. A layout from before windows were pinned pins them all if it was saved in multi-window mode, and none if not.
Layouts are JSON files in $XDG_CONFIG_HOME/bobview/layouts (by default
~/.config/bobview/layouts, and on Windows %APPDATA%\bobview\layouts), so
they can be copied between machines or kept under version control.
Positions on Wayland and with changed monitors
A saved position that is on none of the monitors now (a laptop that was docked) is dropped and the window manager places the window.
Wayland does not let an application read or set its windows' positions, so on a Wayland desktop a layout restores the windows and their sizes but the compositor places them, and bobview logs this once. To keep positions, run bobview under XWayland:
WAYLAND_DISPLAY= bobview
X11 desktops, including bobview-ssh over X forwarding, restore positions.
Trend PVs
A trend window plots one or more PVs against time: their archived history
from an EPICS Archiver Appliance,
joined to their live values. The plot is
archiveview's, so a trend in
bobview works as the standalone archiveview does.
Open a trend
- Right-click a widget (long-press on a touch screen) and pick Trend for its PV, or Trend with rule PVs for its PV and the PVs its rules read. The same menu adds them to a trend already open.
- In a BOY screen, a button that would open a Data Browser or StripTool opens a trend of its PVs instead (see How BOY screens are translated).
Several trends may be open at once, each in its own window over the display, and they stay open as you move between displays. Layouts save them.
Give bobview an archiver
Without an archiver a trend is live only: each PV's line starts when the window opens. To see history, name the appliance's retrieval service:
bobview --archiver http://archiver.example.com:17668 DISPLAY
/retrieval is added unless the URL ends with it, and Phoebus's
pbraw://host:port/retrieval form works too. The archiver is chosen in this
order:
--archiver URL, which is also remembered for later runs (not with--snapshot);- none with
--no-archiver, for this run only; - the
BOBVIEW_ARCHIVERenvironment variable (not remembered), for a site-wide default set in a launcher script or profile; - the last archiver given with
--archiveror picked in a trend's archiver pill, fromsettings.jsonbeside the layouts.
The pill in each trend's top bar shows the archiver and whether it can be
reached (green), is being checked, or cannot be reached (red). Click it to
switch to another archiver, or to none; the switch applies to every trend
window, and is remembered for the next run unless --archiver or
BOBVIEW_ARCHIVER names another.
To try it without an appliance, run archiveview's mock archiver, which
serves synthetic history for the test IOC's bl01t:SUM and a few
MOCK: PVs:
cargo install archiveview-mockarchiver
archiveview-mockarchiver &
bobview --archiver http://127.0.0.1:17668
Use a trend
- Time. The top bar's presets show the last hour, 8 hours, day or week;
⋯ takes a span such as
90mor2 days, or two dates. Drag to pan and scroll to zoom time about the pointer. Moving into the past leaves Live; click it (or pressL) to follow now again. - Values. Each value axis fits its data until you zoom it: drag an axis, or shift-scroll over it. PVs with different units or scales get axes of their own (up to four), on a log scale where that suits the values. Double-click the plot to reset.
- PVs. Type in the search box to add a PV; with an archiver it also searches the archived names. The legend chips under the plot give each PV's colour, name and value: click one to hide its line, hover to pick it out, and × to remove it. Ctrl+V over the plot adds the PVs named in the clipboard.
- Middle-click. On Linux (X11 or Wayland) a middle-click on the plot or
legend adds the PVs named in the primary selection, as in the standalone
archiveview: middle-click a widget in a display to select its PV name, or
select names in a terminal or editor, then middle-click the trend. Channel
Access, PV Access,
loc://andsim://names are added (asim://one such assim://sine(0, 10, 0.1)whole, with its arguments); a formula (=oreq://) isn't: use Trend in the widget's right-click menu. A middle-click in the search box pastes the selection there, and text selected in the search box becomes the primary selection. A note over the plot says what was added, or that the selection named no PVs (one naming more than 20 adds none). Windows and macOS have no primary selection, so there use Ctrl+V; in a snapshot a middle-click on a trend pastes nothing. - Undo. ↶ and ↷ (or Ctrl+Z and Ctrl+Shift+Z) undo and redo changes to the plot.
- Hover over the plot for a readout of every PV at that time.
Limits
- A PV the archiver doesn't hold is plotted live only, and its chip says so.
- Waveforms are not plotted: their chip shows the latest array.
- In the browser build trends are live only: a browser can't reach the archiver, and a middle-click pastes nothing: use Ctrl+V.
Deploy bobview-server for others
Serve displays to a browser runs bobview-server
for this machine only. This guide makes it reachable from others, safely.
All of its options are in Command line.
Run it where PVs are reachable
The server reaches PVs through bobview's own CA/PVA client, so run it
anywhere caget works, with the usual EPICS_* variables:
bobview-server --opi /path/to/displays --bind 0.0.0.0
The release tarball includes the web build, which the server finds in
share/bobview/web. Files in --opi take precedence over files of the same
name in the web build (which has the test displays).
Choose who can reach it
By default it listens on --bind 127.0.0.1 --port 8080. Use --bind 0.0.0.0
(every interface) or a specific address to serve others, ideally behind a
firewall or an authenticating reverse proxy, since reading PVs needs no
login.
Browsers let any page open a websocket, so the server accepts only pages
from itself (by Origin), plus any you name:
bobview-server --opi DIR --bind 0.0.0.0 --allow-origin https://ops.example.com
Those pages must also call the server by an IP address, localhost, this
machine's name (as the kernel has it, without its domain, or in a domain of
/etc/resolv.conf), the host of an --allow-origin, or a name given with
--host NAME (repeatable), so that a page cannot reach it through a DNS
name of its own.
Behind a reverse proxy
- Give the public name the proxy passes on as
Hostwith--host NAME. - Every client has the proxy's address, so raise
--max-per-ip(default 10) and expect the logs to show the proxy.
Allow writes
Writes are off unless the server has --write, since they go out as the
server's user. Each write must then carry a token. Set it in the environment
rather than with --write-token, since arguments show in ps:
BOBVIEW_WRITE_TOKEN=s3cret bobview-server --opi DIR --write
With neither, the server makes a random token and prints it to stderr at
startup; an empty BOBVIEW_WRITE_TOKEN counts as unset. Users open the page
with &token=TOKEN. See Writes and safety for
how the token is protected.
Limits it enforces
- At most 100 clients and 50,000 subscribed PVs.
- At most 10 clients from one address (or one IPv6 /64),
--max-per-ip N. - A client subscribing to more than 1,000 new PVs a second, after its first 10,000, is slowed down.
- A client that stops answering websocket pings, or takes over 10 s to take one message, is dropped.
- PV names over 256 characters, or with spaces or non-ASCII characters, are refused.
- Each client gets the PVs that changed at most every
--throttle-ms(100 ms), each with its latest state.
It serves no loc:// or sim:// PVs: the page makes those itself. Images
from PVA are not sent over PVWS yet.
Files it will not serve
Dot-files such as .git are never served, nor files reached through a
symbolic link that leads out of --opi or --web (unless
--follow-symlinks). Other sites cannot frame the pages (X-Frame-Options,
frame-ancestors 'self').
Take snapshots
--snapshot renders a display to a PNG without opening a window, then
exits. It is useful for documentation, regression checks and CI.
bobview --snapshot out.png screen.bob
Size and scale
bobview --snapshot out.png --size 1920x1080 screen.bob
bobview --snapshot out.png --size auto screen.bob # as a window manager would size it
bobview --snapshot phone.png --scale-factor 2.75 screen.bob # as on a high-DPI phone
--size defaults to 1280x800. auto starts at the window's default size
and resizes it as a window manager would, on a monitor of 1920x1080 points.
Wait for PVs
PVs get 2 seconds to connect by default; give more with --wait:
bobview --snapshot out.png --wait 5 screen.bob
Click first
--click X,Y, --right-click X,Y and --middle-click X,Y (repeatable,
in the order given) interact with the display before the snapshot, which is
taken --settle seconds (default 1) after the last click. Positions are in
pixels.
bobview --snapshot menu.png --right-click 200,150 --settle 0.5 screen.bob
Snapshot every test display
CI does this against bobview-testioc:
bobview-testioc &
export EPICS_CA_ADDR_LIST=127.0.0.1 EPICS_CA_AUTO_ADDR_LIST=NO
for f in tests/displays/*.bob; do
bobview --snapshot "snapshots/$(basename "$f" .bob).png" --size 1280x800 --wait 5 "$f"
done
kill %1
Build from source
You need a stable Rust toolchain (rustup).
git clone https://github.com/gilesknap/bobview
cd bobview
cargo build --release
cargo test --workspace
The binaries are in target/release.
Cross-build for the phone
From x86_64, with zig and cargo-zigbuild installed:
cargo zigbuild --release --target aarch64-unknown-linux-gnu.2.36
Build the browser version
./scripts/build-web.sh
writes dist/web. It needs the wasm32-unknown-unknown target and the
wasm-bindgen CLI matching the version in Cargo.lock. In a checkout,
bobview-server finds dist/web itself; --web DIR picks another.
Build these docs
The docs are an mdBook, with diagrams drawn by mdbook-mermaid. Install the versions CI uses (newer mdbook-mermaid needs a newer mdBook):
cargo install mdbook@0.4.40 mdbook-mermaid@0.15.0
scripts/build-docs.sh serve --open
This builds the Rust API docs into the book under api/, then serves it;
mdbook serve rebuilds and reloads the page as you edit. Run it again to
pick up changes to the API docs. scripts/build-docs.sh alone builds the
book into docs/book.
Checks CI runs
cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo clippy -p bobview-pv -p bobview --target wasm32-unknown-unknown -- -D warnings
cargo test --workspace
How the docs are published
The Docs workflow builds the book and the API docs on every pull request.
On a push to main or a v* tag it publishes them to the gh-pages branch
in a folder named after the branch or tag, writes switcher.json (the list
of versions for the menu in the top bar) and leaves the other versions in
place. The site's root redirects to main/.
Make a release
Set the version in the workspace's Cargo.toml, in both places: the
workspace version and the = versions of bob and pv under
[workspace.dependencies]. Merge that to main, then push a matching tag:
git tag v1.2.3
git push origin v1.2.3
CI checks that the tag matches the version in Cargo.toml and fails the
build if not. It creates the GitHub Release itself and builds on a runner
for each platform (ubuntu-24.04, ubuntu-24.04-arm, macos-15, which
also cross-builds the x86_64 Mac build, and windows-2022), attaching a
bobview-<arch>-<os>.tar.gz for each Linux and macOS build, such as
bobview-x86_64-linux.tar.gz and bobview-aarch64-macos.tar.gz, and
bobview-x86_64-windows.zip for Windows.
The Linux builds use cargo zigbuild to link against glibc 2.28, so they
run on RHEL 8+, Ubuntu 20.04+ and Debian 10+. Each archive carries
THIRD_PARTY_LICENSES.txt, generated by scripts/third-party-licenses.py.
install.sh downloads the latest release for the machine it runs on.
The tag also publishes a copy of these docs at
https://gilesknap.github.io/bobview/<tag>/, which the version menu in the
docs' top bar then lists beside main.
Publish to crates.io
The workspace publishes five crates: bobview, bobview-server, the
libraries they use, bobview-bob and bobview-pv, and bobview-testioc,
which pv's tests use (the names bob and pv were taken; the code still
calls them bob, pv and testioc). Check them first with
cargo publish --workspace --dry-run
which packages each crate and builds it as crates.io will.
The first time, publish by hand: log in to crates.io with GitHub, create an API token with the publish scope, then
cargo login
cargo publish --workspace
Cargo publishes them in dependency order. After that, CI can publish each release itself with trusted publishing, which needs no stored token:
- On crates.io, in each crate's Settings → Trusted Publishing, add the
repository
gilesknap/bobviewand the workflowci.yml. - In the repository's Settings → Secrets and variables → Actions →
Variables, set
PUBLISH_CRATEStotrue.
The publish job then runs after the GitHub Release is made for each tag.
A version can be published to crates.io only once, so fix a bad release
with a new version.
archiveview
Trend windows embed archiveview
from crates.io, and archiveview depends on the crates.io bobview-pv. The
root Cargo.toml patches that to crates/pv, so both use one PvManager.
That holds only while archiveview's bobview-pv requirement matches this
workspace's version: if pv goes to a new major version, or its API that
archiveview uses changes, release archiveview against it first. Then raise
the archiveview version in the root Cargo.toml, run
cargo update -p archiveview -p archiveview-archiver, and check
cargo tree -i bobview-pv shows a single bobview-pv before tagging.
Scope and fidelity to Phoebus
What bobview is for
bobview is a viewer. It opens Phoebus Display Builder .bob screens and
CS-Studio BOY .opi screens and shows live EPICS values on them, from a
single small binary that starts quickly and runs on modest hardware: down to
a phone, in the Android 16 Linux Terminal (Debian 13, aarch64). The same code
builds for the browser.
What is out of scope
- Scripts (Jython and JavaScript) are not run. The status line shows "N widgets with scripts ignored", counting widgets, not scripts. A few scripts that are common in DLS and techui-builder screens are recognised by name and done natively instead; see How BOY screens are translated and Widgets.
- Display editing. Use Phoebus to edit screens.
- Alarm services, and archivers other than the EPICS Archiver Appliance. Trends show history from an Archiver Appliance through archiveview; see Trend PVs.
Widgets bobview can't draw are shown as a grey placeholder labelled with the widget type and PV name, and the status line counts "N unsupported widgets". What is and isn't supported is listed in Widgets.
How close it is to Phoebus
Fidelity is approximate. Sizes, fonts (scaled by 0.95) and layout were tuned by eye, and output has not been compared with Phoebus's own pixel for pixel.
Where behaviour matters more than looks, bobview follows Phoebus closely:
number formats, how typed text is parsed into a value (units dropped, 0x
and 0b numbers, enum labels), macro expansion, and the precedence of
operators in rules (as Phoebus's Jython for a .bob, as BOY's JavaScript
for an .opi). Where it departs from Phoebus it says so in the reference,
for example rules taking the integer part of a double for bitwise operators
where Jython would fail.
Protocol maturity
Channel Access and PV Access come from the third-party
epics-rs crates, which are young; see
Architecture. bobview has been used against real
Diamond Light Source systems: the screens of beamlines p47 and b01-1, and
the accelerator's diagnostics and vacuum display hierarchies. In CI, the
interop job (crates/pv/tests/interop.rs) reads a PV over CA and over PVA
from a PVXS softIocPVX, writes one over CA and reads it back, and reads one
over CA through a ca-gateway; everything else is tested against the in-repo
bobview-testioc.
Architecture
bobview is a Cargo workspace of five crates. Three are published under
longer names, as bob and pv were taken on crates.io, but keep their short
names in code: bob is bobview-bob, pv is bobview-pv and testioc is
bobview-testioc.
graph LR
bobview["bobview<br/>viewer and bobview-probe"]
server["bobview-server"]
bob["bob<br/>display model"]
pv["pv<br/>PV access"]
testioc["testioc<br/>test IOC"]
egui["egui / eframe"]
archiveview["archiveview<br/>trend plot"]
epics["epics-rs<br/>epics-ca-rs, epics-pva-rs"]
pvws["PVWS"]
bobview --> bob
bobview --> pv
bobview --> egui
bobview --> archiveview
archiveview --> pv
server --> pv
pv -- desktop --> epics
pv -- browser --> pvws
server -. serves .-> pvws
testioc --> epics
classDef wasm stroke-width:3px,stroke-dasharray:6 3
class bobview,bob,pv wasm
| crate | what |
|---|---|
bob | the display model: reads .bob files and translates .opi ones into the same form, expands macros, resolves Eclipse project links and DLS launchers |
pv | PV connections behind one interface: Channel Access, PV Access, PVWS for the browser, loc://, sim:// and formula PVs |
bobview | the viewer: an egui app that draws displays, plus bobview-probe |
bobview-server | serves the web build, .bob files and a PV Web Socket from one port |
testioc | bobview-testioc, a pure-Rust IOC serving the example-services BL01T PVs |
bobview's trend windows are archiveview's
Plot widget, which fetches archived history from an EPICS Archiver
Appliance and joins it to live values. It is a separate project that takes
bobview's PvManager, so the workspace patches its crates.io bobview-pv
to the one in crates/pv, making them the same type.
The crates with a dashed outline also build for wasm32-unknown-unknown.
Which source file does what is listed in the
source map.
From file to screen
graph LR
fetch["fetch<br/>file or http(s)"] --> parse["bob<br/>parse; .opi translated;<br/>project links"]
parse --> build["screen::Builder<br/>macros, rules"]
build --> nodes["nodes<br/>with PV handles"]
nodes --> draw["widgets draw<br/>each pass"]
draw -- Requests --> app["the app<br/>open, trend, close …"]
bobviewreads a display from a file or over http(s), off the UI thread, andbobparses it into a tree of widgets with their properties. An.opiis translated as it is read, so everything after this sees only the.bobform. Files a display names are found through its Eclipse project's linked folders.- The screen builder expands macros down the tree, compiles each widget's
rules and asks
pvfor its PVs, giving a tree of nodes that each hold their PV handles. Embedded displays are built on threads of their own. - Each pass, the widgets read their PVs' latest values and draw. A rule is
evaluated again only when one of its PVs has changed. What a click
asks for (open a display, show a trend, close the window) goes back to
the app as a
Request.
A window is drawn again only when something it shows changes; see Threads, windows and repaints.
Rules are expressions over a widget's PVs, evaluated in bobview, that set
one of its properties. Formula PVs (=expr, eq://expr) are PVs made by
pv from other PVs; see Macros, rules and PV types.
Protocols
pv's ca.rs and pva.rs are thin adapters over the
epics-rs epics-ca-rs and
epics-pva-rs crates, which do the protocol work. These are young, so
protocol correctness rests largely on them; the interop CI job tests
against other EPICS implementations.
In the browser
bob, pv and bobview build for the browser, drawn with WebGL. A browser
can't speak CA or PVA, so there PVs come over a
PV Web Socket (PVWS), as in Daedalus.
bobview-server provides one at /pvws/pv, reaching PVs through the same
pv crate as the desktop viewer; any other PVWS server works too. loc://
and sim:// PVs are made in the page itself. There is one window, and
displays are fetched without blocking the page.
The test IOC
bobview-testioc serves enough of the
epics-containers example-services
BL01T beamline for its screens, over CA and PVA, with no C dependencies. It
runs natively on an aarch64 phone, where the real amd64 IOC containers would
need qemu, and CI uses it to snapshot every test display. See
bobview-testioc.
Design choices
- egui, immediate mode. Each pass draws the display from its nodes and the PVs' latest values, with no retained widget tree to keep in step. It gives one code base for the desktop, the browser and headless snapshots, from a small binary.
- Thin adapters of our own over epics-rs, rather than bindings to the C
libraries: no C toolchain, easy cross-builds, and one
pvinterface for every PV type. - Images are decimated to at most 2048 pixels a side before they are uploaded as a texture.
- Fidelity is tuned by eye against Phoebus, not pixel for pixel; see Scope and fidelity to Phoebus.
Threads, windows and repaints
egui draws a window only when asked to. This page explains who asks, and the locking that lets several windows share one app. It is for contributors; the user's view is in Pinned windows and the viewer.
PV updates
A PvManager owns a tokio runtime (two worker threads, bobview-pv) with
one task per CA or PVA PV, talking to the epics-rs client. Each monitor
event updates the PV's shared state under its lock and bumps its version
(Shared::update in crates/pv/src/lib.rs). The UI thread never waits on
the network: each pass, widgets copy out the latest state.
Each window subscribes its PVs through its own handle,
PvManager::tagged(notifier), so each PV knows which windows show it. An
update calls the notifiers of those windows only. A window's notifier
(Repaint in app.rs) asks egui for one repaint of that window, 100 ms
later, and asks no more until the window's next pass begins; so a window
is redrawn for its PVs at most about 10 times a second, however fast they
change. When a window shows another display, its handle is retagged, so the
old display's PVs stop repainting it. A notice (a refused or failed write)
calls every window's notifier, so each shows it.
sequenceDiagram
participant T as PV task (tokio)
participant S as PV shared state
participant R as child's Repaint
participant E as egui / eframe (UI thread)
participant A as BobApp (locked)
T->>S: monitor event: update state, version += 1
S->>R: call the tag's notifier
R->>E: request_repaint_after_for(100 ms, child)<br/>only if not asked since its last pass
E->>A: the child's deferred viewport callback:<br/>lock, display_pass
A->>R: begin: may ask again
A->>S: widgets read the latest state, and draw
Windows
The main window is eframe's root viewport. Each display and trend window is
a deferred viewport (show_viewport_deferred) with a pass of its own, so it
is redrawn for its own input and PVs, not when another window is. The app,
BobApp, sits behind one Arc<Mutex<_>> (app::App): each window's pass
locks it. eframe runs the passes one after another on the UI thread, so they
never wait for each other.
Three rules keep this working:
- Never hold the lock while calling
show_viewport_deferred. Where windows are embedded, egui runs the callback at once, inside the caller's pass, and the callback takes the lock. The main window's pass returns the windows to keep open, andApp::drawregisters them after letting go. - Windows open from the main window's pass. A link in a child window adds the new window to the app, and the main window registers it on its next pass. So a pass that changes the windows open or their titles repaints the main window.
- A change to another window repaints it. Each pass compares a small
Shape(windows, what each shows, trend titles, where the PV details dialog is) before and after, and repaints the windows whose part changed.
Embedded mode has one native window: the browser, snapshots and unit
tests (a plain egui::Context embeds). There, display and trend windows are
egui::Windows drawn in the main window's pass, and every repaint is of the
main window.
Other repaint sources
Work on other threads asks for a repaint by viewport id, because a bare
request_repaint there reaches whichever window egui is drawing:
- the image-loader threads (
symbol.rs), which read the SVG and PNG files of symbol and picture widgets, repaint each window waiting for an image; - the navigator's and command launchers' threads repaint the main window.
A window also polls while it waits: a display being fetched or built, an embedded display building on its thread, or a fetch in the browser asks for a repaint every 50 ms until it arrives. Strip charts scroll by asking for the next repaint when the plot has moved a pixel.
A trend window's plot is archiveview's Plot, built on the trend window's
own tagged PvManager, so its PVs repaint only that window. It repaints
itself for the rest: archive fetches run on the pv crate's runtime and
ask for a repaint of the viewport the plot was last drawn in, and a live
plot asks for the next repaint as it scrolls (at most 15 frames a
second). Its Live pill also pulses, at about 15 frames a second, for 3 s
after it goes live and while the pointer is over the plot: so a live trend
in its own native window, with the pointer resting on it, repaints at about
15 frames a second, but only that window.
Where windows are embedded, a trend's plot is drawn in the main window, so
each repaint it asks for itself redraws the whole display: there
Trend::ui holds them to a strip chart's floor, MIN_SCROLL_REPAINT
(0.25 s), with Plot::set_min_repaint_interval. A trend in its own native
window repaints only itself, and keeps the plot's own pace. Repaints for
the user's pans and zooms, and for a fetch finishing, are not held back.
Pinned windows and the viewer
For a control room, several displays can be open side by side, each in a window of its own, and the arrangement saved as a layout. The question is where a link opens its display: in place, or in another window? bobview answers it with one idea: each window is pinned or not.
The rule
A link (an action or menu button, a symbol click …) opens its display by these rules, whatever target the screen gives it:
| Click | Display already open somewhere | Not open |
|---|---|---|
| in a pinned window | raise that window | open in the viewer, or a new window if every window is pinned |
| in an unpinned window | raise that window | open in place |
| shift+click | raise that window | open in a new window |
| ctrl+click | open in place | open in place |
The viewer is the unpinned window used last (focused, opened or
unpinned), so clicking a window once makes it where the next link goes.
"Already open" means the same display with the same macros, in another
window. The macros are all those the display would be opened with: the
link's own, and those of the group, widget or embedded display it is on, so
each gauge of a cell, opened through one gauge screen, gets its own window.
A BOY display's $(DID) doesn't count, since each window gives it its own.
Why it works this way
The main window starts pinned, so it stays where it is while the displays it links to come and go in one other window: an overview on one side and a detail on the other. Pin that one too to keep it, and the next link opens a new viewer. Unpinned, the main window navigates in place, as a single window does. Holding shift opens every display in a window of its own, as the old multi-window mode did.
A display opened in place or in the viewer keeps that window's history for Back and Forward. A link to the display its own window shows reloads it in place, as a Refresh button wants, without adding to Back. BOY Exit buttons close the display window they are in (in the main window they go back, or quit bobview when there is nothing to go back to).
In a browser, and on a touch or small screen, displays have no windows of their own: every link navigates in place, and the pin and layout controls are not shown.
Where new windows go
A display window opens sized to its display at 100%, within the monitor, beside the window pinned last, so a row of small windows grows beside a big one: on the first of its right, below it, its left and above it where there is room on the monitor clear of the pinned windows. Failing that it goes beside the window whose link opened it, then beside the other pinned windows, or where it covers them least. The viewer is sized and placed so again for each display a pinned window's link opens in it.
Wayland doesn't let applications place their windows, so there the compositor does.
Redrawing
Each window is redrawn on its own: for its own input, and when a PV its
display shows changes (at most 10 times a second), not when another window
is. That matters most over ssh -X, where each redraw sends the whole
window over the network.
The controls of each window are listed in The window and its controls.
How BOY screens are translated
One renderer for both formats
An .opi file (CS-Studio BOY) is translated as it is read into the .bob
form, the way Phoebus's own legacy import converts it, so it is drawn by the
same widgets as a .bob. It is accepted everywhere a .bob is: on the
command line, in Open… (which lists .opi files too), in actions and
linking containers, and over http(s). As in Phoebus, a .bob next to a named
.opi file is opened in its place.
How each BOY widget is drawn is listed in BOY widgets.
Colours, fonts and rules
Colours keep the RGB the file gives; a colour with only a name is looked up
in the EDM colors.list DLS converted its screens with, then in the Phoebus
palette. Fonts in pixels keep their size, fonts in points are divided by
1.01, and the Phoebus named fonts (Default, Header 1 …) use the Phoebus
sizes. Rules keep BOY's severity numbers (pvSev0 becomes pvLegacySev0)
and widget.getValue() reads the widget's own PV.
Size and clipping
An .opi keeps its declared width and height, and widgets outside them are
clipped, as in BOY. EDM conversions often leave stray widgets far off the
display, which Phoebus (and bobview, for a .bob) would grow the display to
show. This size is also what Fit, --size auto and an embedding container
that sizes itself to its content use.
Scripts that are done natively
Scripts and JavaScript or Python actions are not run, with these exceptions, recognised and done by bobview itself:
- The EDM Exit button's script (
ScriptUtil.closeAssociatedOPI) goes back to the previous display, or closes the window. - A script that opens a Data Browser on some PVs
(
ScriptUtil.executeEclipseCommandoforg.csstudio.trends.databrowser2.newWindowwith['pvnames', 'A,B'], as the DLS vacuum "Graph" buttons do) opens a trend of them, with their archived history when bobview has an archiver, as the Data Browser would show. - The DLS vacuum widget script
get_pv_name.py, recognised by its file name. Its second PV, a vacuum space's link field such asSR03S-VA-SPACE-01:IONPSTA.INPA, names a group (SR03S-VA-GIONP-01:STA NPP NMSnamesSR03S-VA-GIONP-01). Each time it names a different one, a label shows the group's name, and any other widget reads the group's:STAand takes its name, so its tooltip, right-click menu and the$(name)its click passes asdevicefollow. An EDM symbol of a gauge, ion pump or valve strip shows the frame for that status (0 when disconnected).
A click on a shape, symbol or LED with a BOY click hook runs its actions. A disconnected EDM symbol shows its first frame inside the disconnected border, without Phoebus' overlay.
Eclipse projects and DLS launchers
BOY screens name each other through the CS-Studio workspace: an Eclipse
project whose .project links folder names to the directories that hold
each module's screens. bobview resolves these links itself.
DLS modules have no .project on disk, but the generated launcher
runcss.sh beside a module's screens lists the links it gives CS-Studio
(links="${SCRIPT_DIR}=${project}/${module},…"). bobview reads that list
(it does not run the script, which needs configure-ioc), taking version
from the module's configure/VERSION (else dev). So
…/vacuum/4-100/vacuumApp/opi/opi/diamondVacuumOverview.opi opens as
…/vacuumApp/opi/opi/runcss.sh/vacuum_4-100/vacuum/diamondVacuumOverview.opi:
the CS-Studio path /vacuum_4-100/vacuum/diamondVacuumOverview.opi below the
launcher, and links to other modules work.
This is for BOY screens only: a .bob (even one Phoebus-style in place of an
.opi, or one picked in a launcher project's folder) goes by its real path.
A .project nearer the screen than the launcher, or beside it, wins when it
links the screen (an empty one, as many DLS modules have, does not).
The steps to open such screens are in Open BOY screens from an Eclipse project or DLS module.
Launcher commands
DLS screens open others with command actions that run a launcher script,
such as ./srCellVacuum 04, which ends by running runcss.sh DISPLAY -m MACROS to start CS-Studio. For a local display, bobview runs a copy of such
a script beside a stand-in runcss.sh that only records its arguments, and
opens that display with those macros. These are shell scripts, so they are
not run on Windows, where such commands do nothing.
A command that starts StripTool (strip.py, strip.sh or
buildStripToolconfig.sh and the PV names, as in the DLS vacuum, gauge and
RGA screens) opens a trend of those PVs instead; a last word without a :,
such as log_mbar, is ignored. Other commands are not run.
Running scripts is limited for safety; see Writes and safety.
Writes and safety
A viewer that can write to a control system needs to be predictable about when it does. This page explains the rules.
Every write takes one route. A control or a write_pv action calls
Pv::write (or Pv::write_long_string, for text a string-format widget
writes to a char array). Read-only mode refuses it there, unless it is to a
loc:// PV. Otherwise it goes to the PV's writer: the queue of its backend
task for CA and PVA, which converts the text to the PV's type and sends it;
the value itself for loc://; or the PV Web Socket in a browser. On the
desktop a refused or failed write becomes a message in the status line; the
browser is below.
Read-only mode
--read-only refuses every write to a PV: controls are disabled and a write
action says in the status line why it did nothing. loc:// PVs are exempt,
as they live inside bobview and can't reach a control system.
Access rights
Channel Access reports whether a PV is writable, and controls follow it. PV Access does not report access rights, so such a PV is assumed writable (the PV details say "unknown"), and a put the server refuses is shown as a write error in the status line and the PV details.
Writes are never applied late
A write to a CA or PVA PV that was connected and has dropped is refused at once, rather than queued and applied when it comes back, possibly much later; the status line says so. So is a write while 16 earlier writes to the same PV still wait.
A write to a PV that has yet to connect (as a write_pv action to a PV not
on the display makes) waits for it, up to 5 s over CA, without holding up
the PV's other events. The writes behind it wait in that queue (so while it
waits, a 17th is refused), and fail with it if it gives up.
Displays from the web
A write_pv action and the toggle script write at once, as in Phoebus,
whether the display came from a local file or an http:// or https://
URL. To open displays without letting them write, use --read-only, which
refuses every write.
Commands
No command is ever run for a display fetched over http(s), nor in a
command_directory on the web. Only a launcher script that resolves (after
macros, .. and project links) to a local file beside or under the
display, or in a linked folder of its Eclipse project, is run. Anything
else, such as an absolute path elsewhere or a ../.. escape, is refused and
logged, as is a script that any user may change (world-writable, or in a
world-writable folder). Each script run is logged.
In the browser
bobview-server writes as its own user, so it allows writes only with
--write, and each write must carry a token. A page with a token loads
displays only from its own server, and sends the token only to a PVWS on
its own server, so a link naming someone else's display or &pvws= cannot
write with it. The page removes the token from its address after reading
it.
Writes to a PV that was connected and has dropped fail at once, and at most 16 may wait for one PV, as on the desktop. One to a PV that has yet to connect fails after 5 s, which the page sees only as the PV's write error, not as a message.
Command line
bobview
bobview [OPTIONS] [DISPLAY]
DISPLAY is a .bob or .opi file, or an http:// or https:// URL.
With no display, bobview restores the default window layout when one has
been saved, else opens index.bob from the bundled displays
(~/.local/share/bobview/displays) when present, else a file chooser.
| option | meaning |
|---|---|
-m, --macros P=X,R=Y | macros for the display |
-p, --project DIR | open the display through the linked folders of the Eclipse project in DIR (its .project, or for a BOY .opi screen the links of a DLS runcss.sh in DIR); see Open BOY screens |
-z, --zoom FACTOR | fixed zoom, 0.2 to 5, instead of fitting the window |
-f, --fullscreen | start full screen |
--read-only | refuse every write to a PV except loc:// ones; see Writes and safety |
--archiver URL | the EPICS Archiver Appliance trends fetch history from, e.g. http://archiver:17668 (/retrieval is added); remembered for later runs (not with --snapshot). See Trend PVs |
--no-archiver | no archiver this run, even if BOBVIEW_ARCHIVER or the settings name one: trends are live only |
--layout NAME or FILE | restore the saved layout NAME, or the layout in the JSON file FILE (a path with a directory in it or ending in .json); Save layout then writes back to that file. See Save and restore a layout |
--no-layout | don't restore the default layout |
--ui-scale F or auto | size of the toolbar and menus relative to the desktop's scale, 1 being the desktop's own (default auto: smaller on a small high-DPI screen) |
--snapshot OUT.png | render to a PNG without a window and exit; see Take snapshots |
--size WxH or auto | snapshot size in pixels (default 1280x800); auto sizes it as a window manager would, on a 1920x1080 monitor |
--scale-factor F | snapshot as on a screen with this desktop scale, e.g. 2.75 for a phone (default 1) |
--wait SECONDS | time for PVs to connect before a snapshot (default 2) |
--click X,Y | click here before the snapshot (repeatable) |
--right-click X,Y | right-click here before the snapshot (repeatable) |
--middle-click X,Y | middle-click here before the snapshot (repeatable) |
--settle SECONDS | time after the last click before the snapshot (default 1) |
-h, --help | show help |
-V, --version | show the version |
bobview-ssh
bobview-ssh [OPTIONS] [DISPLAY]
Runs bobview with the same arguments, rendering with Mesa in software. See
Choose bobview or bobview-ssh.
bobview-probe
bobview-probe [--seconds N] [--put NAME=VALUE]... NAME...
Prints live values of PVs through bobview's PV layer, for N seconds
(default 5). --put writes a value first (repeatable). PV names take the
same prefixes as in a display (pva://, loc:// …).
bobview-server
bobview-server [OPTIONS]
Serves the bobview web build, .bob files and a PV Web Socket on one port.
Open http://HOST:PORT/?display=FILE.bob in a browser (default
index.bob). See Deploy bobview-server for others.
| option | meaning |
|---|---|
-p, --port PORT | port to listen on (default 8080) |
--bind ADDR | address to listen on (default 127.0.0.1, this machine only; 0.0.0.0 is every interface) |
--web DIR | the web build from scripts/build-web.sh (default ../share/bobview/web next to this binary, else dist/web) |
--opi DIR | serve .bob files from here; they take precedence over files of the same name in the web build |
--follow-symlinks | serve files through symbolic links that lead out of --opi or --web (refused by default) |
--write | let browsers write to PVs (off by default, as in PVWS); every write must carry the write token |
--write-token TOKEN | the token --write wants (default: $BOBVIEW_WRITE_TOKEN if set and not empty, else a random one printed at startup); prefer the variable, as arguments show in ps |
--allow-origin ORIGIN | also let pages from ORIGIN connect, e.g. https://host.example.com (repeatable) |
--host NAME | let browsers call this server NAME, as well as an IP address, localhost, this machine's name and the hosts of --allow-origin (repeatable) |
--max-per-ip N | most clients at once from one address (default 10) |
--throttle-ms MS | least time between two batches of updates to one client (default 100) |
-h, --help | show help |
-V, --version | show the version |
bobview-testioc
bobview-testioc [--only ea|motor|cam]...
Serves the simulated BL01T PVs; by default all three parts. See bobview-testioc.
Environment variables
EPICS
bobview, bobview-probe and bobview-server read the standard EPICS client
variables:
| variable | meaning |
|---|---|
EPICS_CA_ADDR_LIST | hosts or broadcast addresses to search for CA PVs |
EPICS_CA_AUTO_ADDR_LIST | NO to search only EPICS_CA_ADDR_LIST |
EPICS_CA_NAME_SERVERS | host:port of CA name servers (gateways) reached over TCP |
EPICS_PVA_ADDR_LIST | hosts or broadcast addresses to search for PVA PVs |
EPICS_PVA_AUTO_ADDR_LIST | NO to search only EPICS_PVA_ADDR_LIST |
EPICS_PVA_NAME_SERVERS | host:port of PVA servers reached over TCP |
bobview-testioc reads the server variables: EPICS_CA_SERVER_PORT,
EPICS_CAS_*, EPICS_PVAS_SERVER_PORT / EPICS_PVA_SERVER_PORT (TCP,
default 5075) and EPICS_PVAS_BROADCAST_PORT (UDP search, default 5076).
bobview
| variable | meaning |
|---|---|
BOBVIEW_LOG | log level, e.g. debug for more output |
BOBVIEW_UI_SCALE | as --ui-scale |
BOBVIEW_ARCHIVER | the archiver trends use when --archiver is not given (see Trend PVs); empty counts as unset |
XDG_CONFIG_HOME | layouts are in $XDG_CONFIG_HOME/bobview/layouts (default ~/.config/bobview/layouts; on Windows %APPDATA%\bobview\layouts) |
WAYLAND_DISPLAY | set it empty to run under XWayland, so layouts keep window positions |
Displays opened from files also fall back to environment variables for macros they don't define; see Macros, rules and PV types.
bobview-server
| variable | meaning |
|---|---|
BOBVIEW_WRITE_TOKEN | the write token for --write; an empty value counts as unset |
Tests
The interop tests in crates/pv/tests/interop.rs are skipped unless these
name PVs to read:
| variable | meaning |
|---|---|
BOBVIEW_INTEROP_CA_PV | a PV served by the IOC over Channel Access |
BOBVIEW_INTEROP_PVA_PV | a PV served by the IOC over PV Access (PVXS) |
BOBVIEW_INTEROP_GW_PV | a PV reached through ca-gateway (CA) |
BOBVIEW_INTEROP_WRITE_PV | optional writable numeric PV; set to 7 and read back |
BOBVIEW_INTEROP_REQUIRE | 1 to fail instead of skipping when a variable is missing |
Widgets
The Phoebus Display Builder widgets bobview draws, and how far it goes with
each. BOY .opi widgets are translated to these; see
BOY widgets.
| widgets | notes |
|---|---|
| label, textupdate, textentry | formats: default, decimal, exponential, engineering, hex, compact, string, sexagesimal (also HMS and DMS), binary, significant; units, precision; typed text is parsed as Phoebus does (units dropped, 0x/0b numbers, enum labels) |
| led, multi_state_led, byte_monitor | |
| action_button, bool_button, slide_button, choice, radio, combo, checkbox | open display, write PV and open webpage actions; command actions only for DLS launcher scripts and StripTool (see How BOY screens are translated) |
| scaledslider, scrollbar | set by a click or drag with the primary (left) button |
| spinner | its arrows (on the left with buttons_on_left), or Up and Down while it has focus, step the value by increment within its range; or type a value and press Enter. As in Phoebus, a drag does nothing |
| linearmeter | scale, warning regions from the levels (the PV's alarm limits with limits_from_pv), limit markers, needle; horizontal or vertical |
| meter | a dial: scale (linear or log_scale), warning regions from the levels (the PV's alarm limits with limits_from_pv), needle |
| progressbar, tank, thermometer | one shared bar renderer (tank and thermometer are vertical bars with their own default fill colours) |
| xyplot | line, step and bar traces with markers against index or an x PV; several y axes, autoscale, log scale, grid, legend |
| stripchart | its traces' PVs over the last time_range, sampled from when the display opened, on the first y axis (autoscale or fixed, log scale, grid), legend |
| rectangle, ellipse, arc, polyline, polygon | dashed and dotted line_style for rectangles and polylines |
| group, tabs, embedded, navtabs | macros pass down; embedded resize modes 0–2 and 4 (crop) |
| image | PVA NTNDArray (uncompressed, mono or RGB1) or a CA array shaped by data_width/data_height; Phoebus colour maps, autoscale or fixed range, unsigned, colour bar with a scale; toolbar to zoom, pan and place a crosshair that reads out the pixel value; decimated when a side is over 2048 pixels, to keep drawing fast |
| symbol, picture | SVG or PNG files, local or fetched next to the display; the PV value picks the symbol (initial_index, array_index, fallback_symbol; an empty one draws nothing), rotation, preserve_ratio/stretch_image, opacity, overlay for a disconnected PV; run_actions_on_mouse_click runs the symbol's actions when clicked; images inside an SVG are drawn only when embedded (data: URLs); a strip of frames sub_image_width wide (DLS EDM symbols) shows frame image_index |
Common to all widgets
border_width/border_color borders, alarm borders, dashed borders for
disconnected PVs, and label and text update rotation_step. Macros and
rules are described in Macros, rules and PV types.
Unsupported
Unsupported widgets, such as table, webbrowser, array, fileselector and byte-array, are drawn as a grey placeholder labelled with the widget type and PV name, and counted in the status line.
Not yet
- PV Access field paths (
pva://name/field), andsys://and other PV types (shown as not supported) - compressed (codec) images, image axes, ROIs and cursor PVs
- the XY plot
markers, error bars and toolbar - the strip chart's toolbar and second y axes
- GIF and JPEG images (symbols and pictures show their fallback or a placeholder), and text inside SVG images
- scripts: an
executeaction logs that it is not supported, except the "toggle the widget's PV" script that techui-builder puts in synoptic symbols, which bobview recognises and runs itself
BOY widgets
How each CS-Studio BOY widget in an .opi is drawn. Background on the
translation is in How BOY screens are translated.
| BOY widget | drawn as |
|---|---|
| Rectangle, RoundedRectangle, Ellipse, arc, polyline, polygon | the shapes (BOY borders become outlines, points made relative, alpha) |
| Label, TextUpdate, TextInput, NativeText | label, textupdate, textentry (format_type, precision_from_pv, borders inside the bounds, rotation_angle); a text update or input with text and no PV is a label |
| groupingContainer, linkingContainer | group (style from border_style), embedded (opi_file, resize_behaviour; a title bar or group box border becomes a group around it) |
| ActionButton, NativeButton, MenuButton, BoolButton, ImageBoolButton, BoolSwitch | action_button (the push_action_index action first; an EDM message button, a BoolButton with no PV, becomes one that writes its push action), combo or action button with a menu, bool_button, slide_button |
| choiceButton, radioBox, checkbox, combo, spinner, scrollbar | choice, radio, checkbox, combo, spinner, scrollbar |
| LED, bytemonitor, progressbar, tank, thermometer, meter, gauge, scaledslider, knob | led or multi_state_led, byte_monitor, progressbar with its value label, tank, thermometer, meter, scaledslider |
| edm.symbolwidget, MultistateMonitorWidget, BoolMonitorWidget, ImageBoolIndicator, Image | symbol (an image strip, or a numbered set of images, nothing for a state without one), picture |
| detailpanel (DLS) | a group with each row's name and content |
| xyGraph, intensityGraph, tab (TabbedContainer) | xyplot (numbered axes and traces, rules on a trace's properties), image, tabs (one grouping container per tab) |
| edm.menumux | combo on its first target PV (writes the item's label) |
| others | placeholders |
Macros, rules and PV types
Macros
$(X)and${X}, with defaults.- Widget properties such as
$(pv_name), and$(actions)in a tooltip. - Macros pass down through groups, tabs and embedded displays.
- In displays opened from files, a macro that isn't defined falls back to the environment variable of the same name (in widget properties and the display name).
- The display name, with macros expanded (or the file name if it has none), is the window title.
Give macros on the command line with -m P=X,R=Y, or in a browser with
¯os=P=X,R=Y.
Rules
Rules are expressions over pv0, pvStr0, pvSev0, pvLegacySev0 … that
set any property, or one item of a list such as symbols[0], including the
position, size and PV. Output expression rules are supported, and macros in
rules are expanded.
- Operators: arithmetic, bitwise (including shifts) and logical.
- Operators bind as in Phoebus's Jython for a
.bob, or BOY's JavaScript for an.opi. - A lone
=is==in a.bob, as Phoebus makes it. - Unlike Jython, bitwise operators take the integer part of a double such
as
pv0, where Jython fails the rule. - In an
.opi,pvSev0becomespvLegacySev0(BOY's severity numbers) andwidget.getValue()reads the widget's own PV.
PV types
| PV name | what |
|---|---|
name | Channel Access |
ca://name | Channel Access |
pva://name | PV Access |
loc://name | a local PV inside bobview, never written to the control system |
sim://… | a simulated PV, such as sim://sine(0, 100, 0.1) |
=expression, eq://expression | a formula PV |
Not yet supported: sys:// and other PV types (shown as not supported), and
PV Access field paths (pva://name/field). In a browser, CA and PVA PVs
come over PVWS, and loc:// and sim:// PVs are made in the page.
The window and its controls
Main toolbar
From the left: Back, Forward, Home and Reload; zoom out, the zoom, zoom in and Fit; the navigator and Open…; the display's name; and on the right the window's pin, the layout list and ☰, a menu of the rest (Auto-size, Bring all windows to front, Close all other windows and the layout actions).
- Clicking the zoom shows the display at 100% and sizes the window to it (and the navigator, when shown).
- A narrow window keeps Back, Home, the zoom and Fit, and moves the rest into ☰.
- On a small high-DPI screen the toolbar and menus are scaled down so they
take a small share of it;
--ui-scale F(orBOBVIEW_UI_SCALE=F) fixes their size.
Window size
The window opens at the size of the first display (when the screen has
room). With Auto-size (in ☰, on by default) it is resized to each
display you open, unless it is maximised or full screen; turn it off to keep
the window's size and scale each display to fit. -z ZOOM fixes the zoom
instead of fitting the window.
On a widget
- Hover to see its PV and value.
- Right-click (long-press on a touch screen) to copy the PV name, its
value or a
caget/pvgetcommand, open the PV details, Trend it, Trend with rule PVs, or add it to an open trend. See Trend PVs for the trend window's own controls. - Middle-click to copy its PV name, as in EDM and MEDM: a middle-click
in a terminal then pastes it, and one on a
trend or in
archiveview plots it (a
Channel Access, PV Access,
loc://orsim://name; for a formula use Trend). The widget flashes and the status line says "Copied name". See Copying a PV name. - On a touch screen, pinch to zoom.
Copying a PV name
A middle-click on a widget, or Copy PV name in its right-click menu, puts the PV name on both the clipboard (for Ctrl+V) and the primary selection (for a middle-click, on Linux). The name is the one other tools take:
- macros expanded, and a Channel Access name bare and whole
(
BL01T-EA-DET-01:COUNT, notca://BL01T-EA-DET-01:COUNT); - a PV Access name keeps its
pva://; - a
loc://PV keeps its prefix but not its type and initial value (loc://x, notloc://x<VDouble>(1.5)); asim://PV is copied whole, its parameters too (sim://sine(0, 10, 0.1)); - a formula copies the PVs it reads, each named as above, not its text.
A widget copies its own PV only, not the PVs its rules read. An XY plot or strip chart, which has no PV of its own, copies its traces' PVs. Several names are separated by spaces.
On X11 bobview holds the primary selection itself, so it is lost when
bobview exits, as with any X application; this works over ssh -X too. On
Wayland bobview uses the compositor's data-control protocol where it has
one, and XWayland's primary selection otherwise. Windows, macOS and the
browser have no primary selection, so there the name goes to the clipboard
only (in the browser, if it allows the page to write to the clipboard).
Middle-click in the browser is untested, and the browser may start
autoscrolling on it.
Display windows
A display window (one opened beside the main window; see Pinned windows and the viewer) has the native title bar only: its toolbar (Back, Forward, Reload, zoom and Home) is hidden until you show it. While the pointer is over the window, small buttons appear in the top-right corner of the display: Back (while the toolbar is hidden), the window's pin, and a chevron that shows or hides the toolbar. The right-click menu on the display's background has Show toolbar and Close. Pinned windows have 📌 before their title.
Keys
| key | action |
|---|---|
| Ctrl+R | reload |
| Alt+← / Alt+→ | back / forward |
| Ctrl+W | close the window (the main window quits bobview and closes its windows) |
| Ctrl+T | show or hide a display window's toolbar |
| shift+click on a link | open in a new window |
| ctrl+click on a link | open in place |
Navigator
The navigator button shows, beside the main window's display, a tree of the displays the home display links to, the displays those link to, and so on.
- A display's links are its widgets' Open display actions, including those in groups, tabs and embedded displays, each display and macros once, named by its button's text (or, in a menu, the action's description).
- A link with no text of its own, or the same text as another link on its
display (a row of identical device symbols), is named by the display it
opens once that has been read: by the words of its title that macros fill
in, such as
LB-VA-IONP-04fromIon Pump Controller - $(device), else by its whole title. The tooltip gives the title, file and macros. - Clicking a node opens its display as a link in the main window would, so with the main window pinned it goes to the viewer; shift and ctrl work as for a link. Displays open in a window are in bold.
- Displays are read from their files, without connecting their PVs, as their nodes are shown, so the tree is read one level ahead of what is open. A display that links back to one above it is marked ↻ and not opened again; one that can't be read is marked ⚠. A command action's display (a DLS launcher script) is found by running its script, so only when its node is opened or clicked.
- The filter box lists the displays read so far whose name or file matches; ⟳ reads them all again.
- Showing or hiding the navigator widens or narrows the window by its width (unless maximised or full screen), so the display keeps its size and zoom.
Status line
Shows write errors and refusals, what a copy has just put on the clipboard, and counts "N widgets with scripts ignored" and "N unsupported widgets" for a display that has them.
Browser page parameters
The web build's page takes these query parameters:
http://HOST:PORT/?display=FILE.bob¯os=P=X,R=Y&zoom=1.5
| parameter | meaning |
|---|---|
display | the display to open, a path relative to the page or a URL (default index.bob); a URL on another server must allow cross-origin requests, and is refused when the page has a write token |
macros | macros, P=X,R=Y |
zoom | a fixed zoom factor instead of fitting the page |
pvws | another PVWS server: host[:port] (meaning wss://host[:port]/pvws/pv, or ws:// when the page is plain http: and the port isn't 443), or a full ws:// or wss:// URL; default /pvws/pv on the page's own server |
token | the write token of a bobview-server started with --write; removed from the address once read |
Without bobview-server
The web build (dist/web from scripts/build-web.sh, or
share/bobview/web in a release) is a static site: serve it from any web
server and point pvws at any PVWS server. The display's server must allow
the page's origin (CORS) unless it is the same server.
Differences from the desktop
- Displays have no windows of their own: every link navigates in place, and the pin and layout controls are not shown.
- Images from PVA are not sent over PVWS yet.
bobview-testioc
A pure-Rust EPICS IOC that serves, over Channel Access and PV Access, a
subset of the PVs of the
epics-containers example-services
BL01T beamline, enough for its opi/demo.bob screen. It has no C
dependencies, so it runs natively on an aarch64 phone where the real amd64
IOC containers would need qemu. Built on the
epics-rs crates; uses about 15 MB RSS
while streaming images.
bobview-testioc [--only ea|motor|cam]... # default: all three
It needs no shell and keeps running with stdin closed. --only applies to
both protocols. Its environment variables are in
Environment variables.
PVs
| part | PVs | how |
|---|---|---|
ea | bl01t:A, bl01t:B, bl01t:SUM | bl01t-ea-test-01's ioc.db verbatim, A=2.54, B=2.61 |
motor | BL01T-MO-SIMC-01:M1, :M2 | real motor record (motor-rs) on a simulated axis |
cam | BL01T-DI-CAM-01:DET:, :ARR:, :PVA: | soft records plus a small driver loop |
The camera is not a real simDetector: it serves the ADBase / NDPluginBase
records the demo screen uses (Acquire, AcquireTime, AcquirePeriod,
ArrayCounter, ImageMode, NumImages, DetectorState_RBV, FilePath, ...) and
counts frames at the acquire period, honouring Single / Multiple /
Continuous. As in its ioc.yaml it starts acquiring continuously at
AcquireTime 0.1.
PV Access
Every record is also served over PVA the way QSRV does it: NTScalar for analog and long records (with display, control and alarm metadata), NTEnum for bi / bo / mbbi / mbbo, NTScalarArray for waveforms; gets, puts and monitors all work.
BL01T-DI-CAM-01:PVA:OUTPUT is an NTNDArray like NDPluginPVA's output. The
image is simulated: 1024x1024 Int8 (byteValue, pixels 0..255 stored as i8
bit patterns, as an Int8 simDetector produces), uncompressed, with drifting
diagonal fringes, three gaussian spots on Lissajous paths and some noise. A
frame is posted for every frame counted (10 Hz by default) while
DET:ArrayCallbacks and PVA:EnableCallbacks are both on; uniqueId is
the frame counter. To look at a frame without a viewer:
cargo run -p bobview-testioc --example frame_png -- /tmp/frame 1 50
Build for the phone
From x86_64:
cargo zigbuild --release --target aarch64-unknown-linux-gnu.2.36 -p bobview-testioc
Rust API
The workspace's crates are documented with rustdoc, built into this book
under api/.
Libraries:
bob: the display model,.boband.opireading, macros and project linkspv: PV connections over CA, PVA, PVWS and local sourcestestioc: the test IOC
Programs, documented with their private items, since that is all they have:
bobview: the viewer, its egui app and widgetsbobview_server: the web server and its PV Web Socketbobview_probeandbobview_testioc: the command-line front ends ofbobview-probeandbobview-testioc
The crates are on crates.io (see Install from crates.io), but their API is not stable; it is documented for contributors.
In a local build the links work when the book is built with
scripts/build-docs.sh (see Build from source),
not with a plain mdbook serve.
Source map
The main source files, for finding your way in. See Architecture for how they fit together.
| file | what |
|---|---|
crates/bob/src/ | |
lib.rs | Display and Widget: a display kept close to its XML, parsed from a file or text |
opi.rs | translates a BOY .opi, element by element, into the .bob form |
macros.rs | $(X) and ${X} expansion |
source.rs | where a display comes from (file or URL), and finding the files it names |
project.rs | Eclipse project linked folders and DLS launcher paths |
group_link.rs, menu_mux.rs, opi_file_script.rs, trend.rs | BOY scripts and launchers recognised and done natively |
crates/pv/src/ | |
lib.rs | PvManager, Pv and PvState: subscriptions, writes and the per-window repaint tags |
ca.rs, pva.rs | Channel Access and PV Access backends over epics-rs |
pvws.rs, pvws_msg.rs | the browser backend over a PV Web Socket |
local.rs, sim.rs, formula.rs | loc://, sim:// and formula PVs |
name.rs, value.rs | PV names and their prefixes; values and their conversions |
crates/bobview/src/ | |
main.rs | the command line, and starting the native or browser app |
app.rs | BobApp: the windows, layouts, requests and repaints (see Threads, windows and repaints) |
view.rs | one window's display, its loading and its Back and Forward |
fetch.rs | reading displays from files or over http(s), and caching them |
screen.rs | the screen builder: widgets with positions, macros, PVs and rules |
rules.rs | the rule expression language |
widgets.rs | painting and interaction for each widget type, and Request |
image.rs, symbol.rs, svg.rs | the image widget; symbol and picture widgets, loaded on worker threads; safe SVG parsing |
plot.rs, meter.rs, stripchart.rs, axis.rs | XY plots, meters, the strip chart and their shared scales |
trend.rs | trend windows: an archiveview Plot each |
format.rs | values as text and back, as Phoebus formats them |
tree.rs | the navigator |
pvmenu.rs | the PV context menu and details window |
layout.rs | saved window layouts |
launch.rs | running DLS launcher scripts for .opi command actions |
snapshot.rs | headless rendering to PNG |
crates/bobview-server/src/ | |
main.rs, files.rs, pvws.rs | the server, its file serving, and its PVWS onto pv |
Licences
bobview is Apache-2.0.
The release binaries include third-party crates, among them the
epics-rs CA and PVA libraries
(EPICS-style open licence), and embed the Liberation Sans fonts and a symbol
subset of Noto Sans Math (both SIL Open Font Licence) and egui's default
fonts. Their notices are in THIRD_PARTY_LICENSES.txt in each release
archive (tarball or zip), generated by scripts/third-party-licenses.py.
tests/displays/demo.bob and the test IOC's ea.db come from
epics-containers example-services (Apache-2.0).
These docs draw their diagrams with Mermaid
(MIT), in docs/theme/mermaid.min.js.