# ###########################################################################
#
# This file is part of Taurus
#
# http://taurus-scada.org
#
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
#
# Taurus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Taurus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Taurus. If not, see <http://www.gnu.org/licenses/>.
#
# ###########################################################################
"""
This module provides several taurus CLI subcommands (plot, trend, image,...)
that show a widget which may have more than one alternative implementation.
The available registered implementations for each command can be listed with
the `--ls-alt` option. Specific implementations can be selected with the
`--use-alt` option.
These commands also honour the default implementation selection configured in
:mod:`taurus.tauruscustomsettings` via the `*_ALT` variables
The alternative implementations can be registered using the following
entry-point group names:
- `taurus.plot.alt`: the loaded object is expected to be a `TaurusPlot`
implementation
- `taurus.trend.alt`: the loaded object is expected to be a `TaurusTrend`
implementation
- `taurus.trend2d.alt`: the loaded object is expected to be a
`TaurusTrend2dDialog` implementation
- `taurus.image.alt`: the loaded object is expected to be a `TaurusImageDialog`
implementation
Expected API for alternative implementations
--------------------------------------------
When registering a widget class to be used for these commands, the classes need
to provide the following minimum API:
- For **all** the classes:
- must be a Qt.QWidget
- must implement `setModel()` and `getModel()` equivalent to those in
:class:`taurus.qt.qtgui.base.TaurusBaseComponent`
- For TaurusPlot:
- should implement `setXAxisMode(mode)` where mode is one of `"n"` or `"t"`
- should implement `loadConfigFile(name)` equivalent to
:meth:`taurus.qt.qtcore.configuration.BaseConfigurableClass.loadConfigFile`
- For TaurusTrend:
- should implement `setXAxisMode(mode)` where mode is one of `"n"` or `"t"`
- should implement `loadConfigFile(name)` equivalent to
:meth:`taurus.qt.qtcore.configuration.BaseConfigurableClass.loadConfigFile`
- should implement `setMaxDataBufferSize(n)` where `n` is an integer
- should implement `setForcedReadingPeriod(period)` where `period` is an
integer
- For TaurusTrend2D:
- should accept the following keyword arguments in its constructor:
- stackMode=x_axis_mode where mode is one of `"d"` or `"n"` or `"t"`
- wintitle=window_name,
- buffersize=max_buffer_size
- For TaurusImage:
- should accept the following keyword arguments in its constructor:
- wintitle=window_name,
- should implement `setRGBmode(mode)` where mode is one of `"gray"`, `"rgb"`
"""
import sys
import click
import taurus.cli.common
from taurus.core.util.plugin import selectEntryPoints
from taurus import tauruscustomsettings as _ts
from taurus import warning, info
from taurus.qt.qtgui.application import TaurusApplication
__all__ = [
"x_axis_mode_option",
"max_buffer_option",
"forced_read_option",
"plot_cmd",
"trend_cmd",
"trend2d_cmd",
"image_cmd",
]
EP_GROUP_PLOT = "taurus.plot.alts"
EP_GROUP_TREND = "taurus.trend.alts"
EP_GROUP_TREND2D = "taurus.trend2d.alts"
EP_GROUP_IMAGE = "taurus.image.alts"
def _print_alts(group):
alts = [ep.name for ep in selectEntryPoints(group)]
print("Registered alternatives :\n {}\n".format("\n ".join(alts)))
def _load_class_from_group(group, include=(".*",), exclude=()):
"""
Factory that returns the first available class from the group entry point.
The selection is done among the classes registered in
the `group` entry-point, prioritized according to the given
`include` and `exclude` patterns
(see :func:`taurus.core.util.plugin.selectEntryPoints`)
"""
eps = selectEntryPoints(group, include=include, exclude=exclude)
for ep in eps:
try:
return ep.load(), ep.name
except Exception:
info("Cannot load %s", ep.name)
raise ImportError("Could not load any class from {}".format(eps))
[docs]
def x_axis_mode_option(choices=("t", "n")):
hlp = {
"n": "regular axis",
"e": "event number",
"t": "absolute time/date axis",
"d": "delta time axis",
}
o = click.option(
"-x",
"--x-axis-mode",
"x_axis_mode",
type=click.Choice(choices),
default=choices[0],
show_default=True,
help=(
"X axis mode: "
+ ", ".join([k + " for " + hlp[k] for k in choices])
),
)
return o
[docs]
def max_buffer_option(default):
o = click.option(
"-b",
"--buffer",
"max_buffer_size",
type=int,
default=default,
show_default=True,
help=(
"Maximum number of values to be stacked "
+ "(when reached, the oldest values will be "
+ "discarded)"
),
)
return o
forced_read_option = click.option(
"-r",
"--forced-read",
"forced_read_period",
type=int,
default=-1,
metavar="MILLISECONDS",
help="force re-reading of the attributes every MILLISECONDS ms",
)
@click.command("plot")
@taurus.cli.common.models
@taurus.cli.common.config_file
@x_axis_mode_option(["n", "t"])
@taurus.cli.common.demo
@taurus.cli.common.window_name("TaurusPlot")
@taurus.cli.common.use_alternative
@taurus.cli.common.list_alternatives
def plot_cmd(
models, config_file, x_axis_mode, demo, window_name, use_alt, ls_alt
):
"""Shows a plot for the given models"""
if ls_alt:
_print_alts(EP_GROUP_PLOT)
sys.exit(0)
if use_alt is None:
use_alt = getattr(_ts, "PLOT_ALT", ".*")
try:
TPlot, epname = _load_class_from_group(
EP_GROUP_PLOT, include=[use_alt]
)
except Exception:
_print_alts(EP_GROUP_PLOT)
sys.exit(1)
app = TaurusApplication(app_name="taurusplot({})".format(epname))
w = TPlot()
w.setWindowTitle(window_name)
if demo:
models = list(models)
models.extend(["eval:rand(100)", "eval:0.5*sqrt(arange(100))"])
try:
w.setXAxisMode(x_axis_mode)
except Exception as e:
warning(
'Could not set X axis mode to "%s" on %s plot. Reason: "%s"',
x_axis_mode,
epname,
e,
)
sys.exit(1)
if config_file is not None:
try:
w.loadConfigFile(config_file)
except Exception as e:
warning(
'Could not load config file "%s" on %s plot. Reason: "%s"',
config_file,
epname,
e,
)
sys.exit(1)
if models:
w.setModel(models)
w.show()
sys.exit(app.exec_())
@click.command("trend")
@taurus.cli.common.models
@taurus.cli.common.config_file
@x_axis_mode_option(["t", "n"])
@taurus.cli.common.demo
@taurus.cli.common.window_name("TaurusTrend")
@taurus.cli.common.use_alternative
@taurus.cli.common.list_alternatives
@max_buffer_option(None)
@forced_read_option
def trend_cmd(
models,
config_file,
x_axis_mode,
demo,
window_name,
use_alt,
ls_alt,
max_buffer_size,
forced_read_period,
):
"""Shows a trend for the given models"""
# list alternatives option
if ls_alt:
_print_alts(EP_GROUP_TREND)
sys.exit(0)
# use alternative
if use_alt is None:
use_alt = getattr(_ts, "TREND_ALT", ".*")
# get the selected alternative
try:
TTrend, epname = _load_class_from_group(
EP_GROUP_TREND, include=[use_alt]
)
except Exception:
_print_alts(EP_GROUP_TREND)
sys.exit(1)
app = TaurusApplication(app_name="taurustrend({})".format(epname))
w = TTrend()
# window title option
w.setWindowTitle(window_name)
# demo option
if demo:
models = list(models)
models.extend(["eval:rand()", "eval:1+rand(2)"])
# x axis mode option
try:
w.setXAxisMode(x_axis_mode)
except Exception as e:
warning(
'Could not set X axis mode to "%s" on %s plot. Reason: "%s"',
x_axis_mode,
epname,
e,
)
sys.exit(1)
# configuration file option
if config_file is not None:
try:
w.loadConfigFile(config_file)
except Exception as e:
warning(
'Could not load config file "%s" on %s plot. Reason: "%s"',
config_file,
epname,
e,
)
sys.exit(1)
# set models
if models:
w.setModel(list(models))
# period option
if forced_read_period > 0:
w.setForcedReadingPeriod(forced_read_period)
# max buffer size option
if max_buffer_size is not None:
try:
w.setMaxDataBufferSize(max_buffer_size)
except Exception as e:
warning(
'Could not set max buffer size on %s trend. Reason: "%s"',
epname,
e,
)
sys.exit(1)
w.show()
sys.exit(app.exec_())
@click.command("trend2d")
@taurus.cli.common.model
@x_axis_mode_option(["d", "t", "n", "e"])
@taurus.cli.common.demo
@taurus.cli.common.window_name("TaurusTrend2D")
@taurus.cli.common.use_alternative
@taurus.cli.common.list_alternatives
@max_buffer_option(512)
def trend2d_cmd(
model, x_axis_mode, demo, window_name, use_alt, ls_alt, max_buffer_size
):
# list alternatives option
if ls_alt:
_print_alts(EP_GROUP_TREND2D)
sys.exit(0)
# use alternative
if use_alt is None:
use_alt = getattr(_ts, "TREND2D_ALT", ".*")
# get the selected alternative
try:
TTrend2D, epname = _load_class_from_group(
EP_GROUP_TREND2D, include=[use_alt]
)
except Exception:
_print_alts(EP_GROUP_TREND2D)
sys.exit(1)
app = TaurusApplication(app_name="Taurus Trend 2D ({})".format(epname))
w = TTrend2D(
stackMode=x_axis_mode, wintitle=window_name, buffersize=max_buffer_size
)
if demo:
model = "eval:x=linspace(0,3,40);t=rand();sin(x+t)"
if model:
w.setModel(model)
w.show()
sys.exit(app.exec_())
@click.command("image")
@taurus.cli.common.model
@taurus.cli.common.demo
@taurus.cli.common.window_name("TaurusImage")
@taurus.cli.common.use_alternative
@taurus.cli.common.list_alternatives
@click.option(
"-c",
"--color-mode",
"color_mode",
type=click.Choice(["gray", "rgb"]),
default="gray",
show_default=True,
help=("Color mode expected from the attribute"),
)
def image_cmd(model, demo, window_name, color_mode, use_alt, ls_alt):
# list alternatives option
if ls_alt:
_print_alts(EP_GROUP_IMAGE)
sys.exit(0)
# use alternative
if use_alt is None:
use_alt = getattr(_ts, "IMAGE_ALT", ".*")
# get the selected alternative
try:
TImage, epname = _load_class_from_group(
EP_GROUP_IMAGE, include=[use_alt]
)
except Exception:
_print_alts(EP_GROUP_IMAGE)
sys.exit(1)
app = TaurusApplication(app_name="Taurus Image ({})".format(epname))
rgb_mode = color_mode == "rgb"
# TODO: is "-c rgb --demo" doing the right thing?? Check it.
if demo:
if color_mode == "rgb":
model = "eval:randint(0,256,(10,20,3))"
else:
model = "eval:rand(256,128)"
w = TImage(wintitle=window_name)
w.setRGBmode(rgb_mode)
# set model
if model:
w.setModel(model)
w.show()
sys.exit(app.exec_())