(user.command_line_interface)=
## Command-Line Interface (CLI)

In the previous sections, you have succesfully started BEC and also already interacted with the CLI to update the BEC device configuration. 
This section aims to explore the CLI capabilities further.

### Start-up
The CLI can be started from a terminal after activating the [previously installed bec_venv](#user.installation) using the shell command within the directory where ``bec_venv`` is installed.
```{code-block} bash
source ./bec_venv/bin/activate
```
```{code-block} bash
bec
```

### Client interface
The CLI is based on the [IPython](https://ipython.org/) interactive shell. 
As seen in the screenshot below, the prompt is prefixed with, e.g. ``demo [4/522] >>``. 
The prefix contains the name of the current session (*demo*), the current cell number (*4*) and the next scan number (*522*).

### Device access
Devices are grouped in ``dev``. 
This allows users to use tab-completion for finding devices.

```{image} ../assets/tab-complete-devices.png
:align: center
:alt: tab completion for finding devices
:width: 300
```

```{hint}
``dev`` is imported as a builtin. As a result, you can access ``dev`` from everywhere. ``dev`` itself is just an alias for ``bec.device_manager.devices``.
```

To get a quick glance at all available devices, you can type
    
```ipython
demo [1/3] ❯❯ dev.show_all()
```

If you want to the current position, setpoint and limits of some device, you can use the ``wm`` command, e.g.
    
```{image} ../assets/wm-devices.png
:align: center
:alt: tab completion for finding devices
:width: 400
```

The ``wm`` command can receive multiple devices as strings, e.g. ``dev.wm(["samx, "samy"])`` or  a single device, e.g. ``dev.wm("samx")``. It also supports regular expressions, e.g. ``dev.wm("sam*")``. Instead of strings, you can also pass the device objects directly, e.g. ``dev.wm([dev.samx, dev.samy])``.


### Inspect a device

To inspect the device samx, you can simply type ``dev.samx`` and you'll get a printout of the relevant information about this device.
```ipython
demo [1/3] ❯❯ dev.samx
Out[1]:
Positioner(name=samx, enabled=True):
--------------------
Details:
	Description: samx
	Status: enabled
	Read only: False
	Last recorded value: {'samx': {'value': -0.0011717217935431634, 'timestamp': 1702306192.450343}, 'samx_setpoint': {'value': 0, 'timestamp': 1702306192.382011}, 'samx_motor_is_moving': {'value': 0, 'timestamp': 1702306192.450175}}
	Device class: SimPositioner
	readoutPriority: baseline
	Device tags: ['user motors']
	User parameter: None
--------------------
Config:
	delay: 1
	limits: [-50, 50]
	speed: 100
	tolerance: 0.01
	update_frequency: 400
```

If you just want to see the current position, setpoint and limits of a device, you can simply type
    
```ipython
demo [1/3] ❯❯ dev.samx.wm
```

#### Read interface

While the device inspection as seen above is an easy way to quickly glance at the current state of a device, it cannot be used programmatically, i.e. within a script. 
For reading from a device, we provide two interfaces: `.read` and `.get`.
Devices are composed of signals, each of their own `kind` with possible values `hinted/normal/config/omitted`. 
It is the `kind` attribute that determines what signals are read out by using e.g. `dev.samx.read()`.
For more details on `device`, `signal` and `kind`, refer to [ophyd](#developer.ophyd).

To read out `hinted` and `normal` signals from a device, use

```ipython
demo [1/50] ❯❯ dev.samx.read()
Out[1]:
{'samx': {'value': 0, 'timestamp': 1701942802.6418009},
'samx_setpoint': {'value': 0, 'timestamp': 1701942802.641365},
'samx_motor_is_moving': {'value': 0, 'timestamp': 1701942802.641365}}
```

and

```ipython
demo [4/50] ❯❯ dev.samx.read_configuration()
Out[4]:
{'samx_velocity': {'value': 1, 'timestamp': 1701942802.641421},
'samx_acceleration': {'value': 1, 'timestamp': 1701942802.641428}}
```

to read the `config` signals.
In both cases, a nested dictionary is returned with value/timestamp pairs for each signal. 
The current position of `samx` is accessed `dev.samx.read()['samx']['value']`.

```{note}
The default behaviour for `.read` and `.read_configuration` is to read the last recorded value from redis, i.e. `cached=True`.
However, we can force an update by using `dev.samx.read(cached=False)` which will introduce additional overhead. 
Signals of type `omitted` are currently not stored in redis, nor are they read from the device using e.g.`dev.samx.read()` and therefore must
be read out directly using e.g. `dev.samx.my_omitted_signal.read()`.
```

In addition, we can read for instance the readback or setpoint value from samx by

```ipython
demo [14/3] ❯❯ dev.samx.readback.read()
Out[14]: {'samx': {'value': -0.0011717217935431634, 'timestamp': 1702306192.450343}}
demo [17/3] ❯❯ dev.samx.setpoint.read()
Out[17]: {'samx_setpoint': {'value': 0, 'timestamp': 1702306192.382011}}
demo [18/3] ❯❯ dev.samx.velocity.read()
Out[18]: {'samx_velocity': {'value': 1, 'timestamp': 1702306158.257976}}
```

which again returns a nested dictionary, however, this time only for the requested signal. 

```{note}
The keys in the returned dictionary are composed of `<devicename>_<signalname>`. 
However, for positioners the signal name <readback> is typically ommited, i,e. see `dev.samx.readback.read()`.
```

#### Get interface

We also provide a more convenient access pattern to values of the devices. 
Similar to `.read()` you may call

``` ipython
demo [20/3] ❯❯ dev.samx.readback.get()
Out[20]: -0.0011717217935431634
```

which will return the value of the readback directly.
You can also retrieve all signals from `samx` via `get`. 

```ipython
demo [13/50] ❯❯ signals = dev.samx.get()
demo [14/50] ❯❯ signals
Out[14]: samx(readback=0, setpoint=0, motor_is_moving=0, velocity=1, acceleration=1, high_limit_travel=50, low_limit_travel=-50, unused=1)
```

which includes all different `kind` of signals from the device.
The return object of `dev.samx.get()` is a [namedtuple](https://docs.python.org/3/library/collections.html) with an access pattern similar to class attributes/properties: `signals.readback`.

```{warning}
We recommend not using `dev.samx.get()` due to the fact that it forces a readback from all signals. 
```

### DeviceConfig

Besides signals, devices are initialized based on their `deviceConfig` (see also [BEC device config](#user.devices)).
The current deviceConfig, e.g. for the device `samx` can be retrieved  either by simply typing 

``` ipython
dev.samx
``` 

or directly by retrieving the deviceConfig through

```ipython
demo [5/50] ❯❯ dev.samx.get_device_config()
Out[5]:
{'delay': 1,
'labels': 'samx',
'limits': [-50, 50],
'name': 'samx',
'speed': 100,
'tolerance': 0.01,
'update_frequency': 400}
```

To update the deviceConfig, please check [set_device_config()](#user.devices.update_device_config).


### Move a motor

A very common operation in the beginning is to be able to move a device. 
For this, there are two variants of device movements: `updated move` and `move`.

#### Updated move (umv)

A umv command blocks the command-line until the motor arrives at the target position (or an error occurs).

```python
scans.umv(dev.samx, 5, relative=False)
```

#### Move (mv)

A mv command is non-blocking, i.e. it does not wait until the motor reaches the target position.

```python
scans.mv(dev.samx, 5, relative=False)
```

```{note}
Be aware of benefits and risks of executing a non-blocking command. A ``CTRL-C`` will not stop its motion, but it needs to be explicitly called via ``dev.samx.stop()`` ``%abort`` or ``%halt``.
```
However, it can be made a blocking call by

```python
scans.mv(dev.samx, 5, relative=False).wait()
```

The same mv command can also be executed by calling the device method `move`

```python
dev.samx.move(5, relative=False)
```

````{note}
mv and umv can receive multiple devices, e.g.
```python
scans.umv(dev.samx, 5, dev.samy, 10, relative=False)
```
````

#### Update motor limits

In order to move motors in a safe manner, you can add software limits to a motor. 
The following command, For example, changes the limits of `samx` to `-50 (low)` and `50 (high)`

``` ipython
dev.samx.limits = [-50, 50]
```

You may also directly access the low and high limits via `dev.samx.low_limit = -50` and `dev.samx.high_limit=50`.
Both access patterns are identical.
Software limits are updated in the device_config, however, when done via command-line this only updates the current device_config session in redis.
To make sure that limits are stored after reloading the device BEC config, you need to update the deviceConfig on disk, please check [bec.config.save_current_session()](#user.devices.export_device_config).

As per default, software limits for motors are set to the values specified in the [BEC device config](#developer.ophyd), subfield device_config. 

````{note}
If no software limits are specified, the motor will be initialized without software limits. 
This is equivalent to having identical values for high and low limits, e.g. 
```python
dev.samx.limits = [0, 0]
```
````

### Run a scan

All currently available scans are accessible through `scans.`, e.g.

```python
scans.line_scan(dev.samx, -5, 5, steps=50, exp_time=0.1, relative=False)
```
You may in addition, scan multiple axis simultaneously, e.g.
```python
scans.line_scan(dev.samx, -5, 5, dev.samy, -5, 5, steps=50, exp_time=0.1, relative=False)
```
which would be a diagonal trajectory in the xy plane, assuming that samx and samy are in an rectangular coordinate system.
There are also multiple ways plot and investigate the data, for this please explore [data access and plotting](#user.data_access_and_plotting). 
This also includes live plotting of data.

BEC has various different type of scans, for instance `scans.grid_scan`, `scans.list_scan`, which you can explore in the simulation. 

#### Explore docstring documentation 
What can be very convenient while exploring built-in scans, is using the [Ipython syntax](https://ipython.readthedocs.io/en/stable/interactive/tutorial.html) `?` to print out all sort of useful information about an object, e.g. for `scans.list_scan` 

```ipython
demo [3/31] ❯❯ scans.list_scan?
Signature: scans.list_scan(*args, parameter: dict = None, **kwargs)
Docstring:
A scan following the positions specified in a list.
Please note that all lists must be of equal length.

Args:
    *args: pairs of motors and position lists
    relative: Start from an absolute or relative position
    burst: number of acquisition per point

Returns:
    ScanReport

Examples:
    >>> scans.list_scan(dev.motor1, [0,1,2,3,4], dev.motor2, [4,3,2,1,0], exp_time=0.1, relative=True)
File:      ~/work_psi_awi/bec_workspace/bec/bec_lib/bec_lib/scans.py
Type:      function
```
The shell printout provides information about the scan signature, parameters, as well as a syntax example at the bottom.

### How to write a script
-----------------------

Scripts are user defined functions that can be executed from the BEC console (CLI). 
They are stored in the ``scripts`` folder and can be edited with any text editor. 
The scripts are loaded automatically on startup of the BEC console but can also be reloaded by typing ``bec.load_all_user_scripts()`` in the command-line.
This command will load scripts from three locations: `~/bec/scripts/.`, `bec/bec_lib/scripts/.` and the beamline plugin directory, e.g. `/csaxs-bec/bec_plugins/scripts/.`


An example of a user script could be a function to move a specific motor to a predefined position:

```python 
    def samx_in():
        umv(dev.samx, 0)
```

or 

```python 

    def close_shutter():
        print("Closing the shutter")
        umv(dev.shutter, 0)
```

A slightly more complex example could be a sequence of scans that are executed in a specific order:

```python

    def overnight_scan():
        open_shutter()
        samx_in()
        for i in range(10):
            scans.line_scan(dev.samy, 0, 10, steps=100, exp_time=1, relative=False)
        samx_out()
        close_shutter()
```

This script can be executed by typing ``overnight_scan()`` in the BEC console and would execute the following sequence of commands:

1. Open the shutter
2. Move the sample in
3. Perform 10 line scans on the sample
4. Move the sample out
5. Close the shutter

### Create a custom scan

As seen above, scans can be access through `scans.`. 
However, sometimes it is necessary to run a sequence of functions as if it were a scan. 
For example, we might want to run a grid scan (2D scan) with our sample motor stages but move the sample position in z after each 2D scan. 
Normally, this would create multiple output files that one would need to merge together later. 

This is where the scan definition comes in. 
It allows us to run a sequence of functions as if it were a scan, resulting in a single `scan_number`, a single `scan_id` and a single output file. 

```python

    @scans.scan_def
    def custom_grid_scan():
        open_shutter()
        umv(dev.samz, 0) # move to samz to start position (absolut)
        for i in range(10):
            scans.grid_scan(dev.samx, 0, 10, 10, dev.samy, 0, 10, 10, exp_time=0.1, relative=False)
            umvr(dev.samz, 0.1) # move samz + 0.1mm after each grid scan
        close_shutter()
```

By adding the decorator ``@scans.scan_def`` to the function definition, we mark this function as a scan definition. 

### Computed Signal
Here, we introduce the `ComputedSignal`, which enables users to effortlessly generate custom signals based on signals from other devices.

To utilize this feature, add a new signal, such as `pseudo_signal`, to the device configuration

``` yaml
pseudo_signal:
  deviceClass: ophyd_devices.ComputedSignal
  deviceConfig:
    compute_method: "def compute_signals(signal1, signal2):\n    return signal1.get()*signal2.get()\n"
    input_signals: 
      - "bpm4i_readback"
      - "bpm5i_readback"
  enabled: true
  readOnly: false
  readoutPriority: baseline
```
The `pseudo_signal` is a `ComputedSignal` where the *readback* is calculated based on the configured *input_signals* and *compute_method*. In the provided example, the *readback* of `bpm4i` and `bpm5i` is multiplied to produce the `readback` of the `pseudo_signal`.

Additionally, we offer users a straightforward interface through the client (CLI) to adjust the *compute_method* and *input_signals*. The process involves two steps:

1. Define the *input_signals* using `dev.<device_name>.set_input_signal`.
2. Upload a method for the *compute_method* via `dev.<device_name>.set_compute_method`.

It's worth noting that users have the option to leverage additional packages such as `numpy as np` and `scipy as sp` for accelerated computations.

Below is an example demonstrating the use of the `pseudo_signal` to compute the sum over a 2D detector (`dev.eiger`), excluding hot pixel values:

``` ipython
def calculate_readback(signal):
    data = signal.get()
    std = np.std(data)
    mean = np.mean(data)
    return np.sum(data[data>mean+3*std])
dev.pseudo_signal.set_compute_method(calculate_readback)
dev.pseudo_signal.set_input_signals(dev.eiger.image)
```
This setup enhances flexibility and efficiency in signal processing, empowering users to tailor computations to their specific needs.
