Metadata-Version: 2.4
Name: PIPython
Version: 2.13.0.2
Summary: Collection of libraries to use PI devices and process GCS data.
Author: Physik Instrumente (PI) SE & Co. KG
Author-email: service@pi.de
Project-URL: Homepage, http://www.physikinstrumente.com
Keywords: PI,PIPython,physikinstrumente,Physik Instrumente,GCS
Classifier: Development Status :: 5 - Production/Stable
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: pyserial==3.5
Requires-Dist: pyusb==1.3.1

# PIPython

The PIPython package is a collection of Python modules for the communication with PI controllers and for processing GCS data. PIPython is compatible with Python 3.12+ on Windows, Linux, and OS X, and without the GCS DLL on any other platform as well.

## Installation

By installing PIPython you agree to the <a href="https://www.physikinstrumente.com/fileadmin/user_upload/physik_instrumente/files/legal/General-Software-License-Agreement-Physik-Instrumente.pdf" target="_blank">license agreement</a>.

In the command window of the PC, enter the following command: `pip install PIPython`

Additionally, a *samples* folder is provided in the source distribution or can be downloaded from [GitHub](https://github.com/PI-PhysikInstrumente/PIPython).

> **Any questions?**
> 
> If you have any questions, don't hesitate – just send an e-mail to `service@pi.de`. We also appreciate your feedback.


# Quick Start

## Requirements

Download these python packages with pip install: 

- PyUSB
- PySocket
- PySerial

Using `pipython.interfaces.piusb`, you can connect to a USB device without needing the GCS DLL. This only works on Linux and requires libusb, which comes with most Linux distributions.

## Establishing communication
Communication with a PI device can be established via the `GCSDevice` class which wraps the GCS DLL functions and provides methods to connect to the device. Instantiate `GCSDevice` with the controller's product code up to the period as a string type argument (e.g., `'C-884'`).

See [Device Connection](connect.md) for further information.

The following example connects to a C-884 series controller, queries its identification string using the `qIDN()` function and closes the connection.
``` python
from pipython import GCSDevice
pidevice = GCSDevice('C-884')
pidevice.InterfaceSetupDlg()
print(pidevice.qIDN())
pidevice.CloseConnection()
```

`GCSDevice` is a context manager which closes the connection if an exception is raised inside the `with` statement. Thus, the example above should rather be written this way:
``` python hl_lines="2"
from pipython import GCSDevice
with GCSDevice('C-884') as pidevice:
	pidevice.InterfaceSetupDlg()
	print(pidevice.qIDN())
pidevice.CloseConnection()
```

See also `quickstart.py`, as well as the other examples in the `samples` subdirectory.

## Arguments

### Setter functions
**GCS 2.0**

Setter functions can be called with the following argument structures:

- a comma-separated dictionary of axes/channels and values
- a comma-separated list of axes/channels and a list of the corresponding values
- a single axis/channel and a single value

``` python
pidevice.MOV({'X': 1.23, 'Y': 2.34})
pidevice.MOV(['X', 'Y'], [1.23, 2.34])
pidevice.MOV('X', 1.23)
```
For numeric axis or channel identifiers, the quotes may be omitted.

``` python
pidevice.MOV({1: 1.23, 2: 2.34})
pidevice.MOV([1, 2], [1.23, 2.34])
pidevice.MOV(1, 1.23)
```

**GCS 3.0**

Setter functions can be called with the following argument structures:

- a comma-separated dictionary of axes and values
- a comma-separated list of axes and a list of the corresponding values
- a single axis and a single value

```python
pidevice.MOV({'AXIS_1': 1.23, 'AXIS_2': 2.34})
pidevice.MOV(['AXIS_1', 'AXIS_2'], [1.23, 2.34])
pidevice.MOV('AXIS_1', 1.23)
```

### Getter functions
Getter functions can be called with the following argument structures:

**GCS 2.0**

- a comma-separated list of axes/channels
- a single axis/channel
- no arguments, which will return the answer for all available axes/channels

For numeric axis or channel identifiers, the quotes may be omitted.

``` python
pidevice.qPOS(['X', 'Y'])
pidevice.qPOS('X')
pidevice.qPOS(1)
pidevice.qPOS()  
```

**GCS 3.0**

- a single axis
- no arguments, which will return the answer for all available axes or channels

``` python
pidevice.qPOS('AXIS_1')
pidevice.qPOS()
```
 
## Return values

**GCS 2.0**

Axes or channel related answers are returned as (ordered) dictionary.

``` python
pidevice.qPOS()
>> {'X': 1.23, 'Y': 2.34}
```

If a getter function is called with arguments, the data types of the arguments are preserved and can be used as keys.
``` python
pos = pidevice.qPOS([1, 2, 3])
print(pos[1])
```

**GCS 3.0**

Axes or channel related answers are returned as (ordered) dictionary.

``` python
pidevice.qPOS()
>> {'AXis_1': 1.23}
```

If a getter function is called with arguments, the data types of the arguments are preserved and can be used as keys.
``` python
pos = pidevice.qPOS('AXIS_1') # only one axis is possible
print(pos['AXIS_1'])
```

**GCS 2.0 / GCS 3.0**

The following example moves all `axes` to their respective `targets` and waits until each motion has finished. It shows how to use only the values from the returned dictionary.
``` python
from time import sleep
# [...]
pidevice.MOV(axes, targets)
while not all(list(pidevice.qONT(axes).values())):
	sleep(0.1)
```

## Some useful information
### Helper functions
With `pipython.pitools` you have some helper functions at hand that make coding more convenient. With `waitontarget()` for instance, the example above can be written like this:

``` python
from pipython import pitools
# [...]
pidevice.MOV(axes, targets)
pitools.waitontarget(pidevice, axes)
```
See also the examples in the `samples` subdirectory.

### Debug logging
To log debug messages on the console just enter these lines prior to calling `GCSDevice`.
``` python
from pipython import PILogger, DEBUG, INFO, WARNING, ERROR, CRITICAL
PILogger.setLevel(DEBUG)
```

### GCSError and error check
By default an "ERR?" command is sent after each command to query if an error occurred on the device which then will be raised as `GCSError` exception. If communication speed is an issue you can disable error checking:
``` python
pidevice.errcheck = False
```
For the handling of `GCSError` exceptions you can use the defines provided by `gcserror` instead of pure numeric values. The difference between the two is:

- `GCSError`: exception class
- `gcserror`: corresponding module
``` python
from pipython import GCSDevice, GCSError, gcserror
with GCSDevice('C-884') as pidevice:
    try:
        pidevice.MOV('X', 1.23)
    except GCSError as exc:
        if exc == gcserror.E_1024_PI_MOTION_ERROR:
            print('There was a motion error, please check the mechanics.')
        else:
            raise
```
The exception class `GCSError` translates the error code in a readable message.
``` python
from pipython import GCSError, gcserror
raise GCSError(gcserror.E_1024_PI_MOTION_ERROR)
>>> GCSError: Motion error: position error too large, servo is switched off automatically (-1024)
```

**GCS 3.0**
You can reset the error state of one or more axes like this:
```python
for axis in device.axes:
    if axis_has_error(device):
        while check_axis_status_bit(device, axis, AXIS_STATUS_FAULT_REACTION_ACTIVE):
            pass
        print('reset axis error: ', axis)
        device.RES(axis)
```

### Big data
Commands like `qDRR()` (GCS 2.0) or `qREC_DAT()` (GCS 3.0) which read a large amount of GCS data, return immediately with the header dictionary containing information about the data. Then they start a background task that carries on reading data from the device into an internal buffer. The `bufstate` property returns the progress of the read process as a floating point number (range 0 to 1) and becomes `True` when reading has finished. Hence, when using it in a loop, check for `is not True`. (Remember, this is not the same as `!= True`.)

- GCS 2.0
``` python
header = pidevice.qDRR(1, 1, 8192)
while pidevice.bufstate is not True:
    print('read data {:.1f}%...'.format(pidevice.bufstate * 100))
    sleep(0.1)
data = pidevice.bufdata
```

- GCS 3.0
``` python
header = pidevice.qREC_DAT('REC_1', 'ASCII', 1, 1, 8192)
while pidevice.bufstate is not True:
    print('read data {:.1f}%...'.format(pidevice.bufstate * 100))
    sleep(0.1)
data = pidevice.bufdata
```

### Textual interface
In addition to using the functions implemented in `GCSCommands` you can send GCS commands as strings to the controller. Use

- `read()` for commands returning a response
- `read_gcsdata()` for commands returning GCS data
- `send()` for non-responding commands
``` python
print(pidevice.read('POS?'))
print(pidevice.read_gcsdata('DRR? 1 100 1'))
pidevice.send('MOV X 1.23')
```
The commands return the raw string or GCS data from the controller. If `errorcheck` is activated the device is automatically queried for its error state. We recommend to use the provided functions instead of sending raw strings.

In line with the C++ GCS DLL the functions `ReadGCSCommand()` and `GcsCommandset()` are also available. They don't query the device for errors.
``` python
print(pidevice.ReadGCSCommand('POS?'))
pidevice.GcsCommandset('MOV X 1.23')
```

# Device Connection

## Connecting to a single device with the GCS DLL

### Via dialog
On Windows systems, the GCS DLL provides a graphical user interface to select the connection parameters.
``` python
from pipython import GCSDevice
with GCSDevice() as pidevice:
    pidevice.InterfaceSetupDlg()
    print('connected: {}'.format(pidevice.qIDN().strip()))
```

If you pass a string as the optional `key` argument to the `InterfaceSetupDlg` method, the DLL stores the settings in the Windows registry and retrieves them the next time you connect with the same key.

``` python
from pipython import GCSDevice
with GCSDevice() as pidevice:
    pidevice.InterfaceSetupDlg('MyTest')
    print('connected: {}'.format(pidevice.qIDN().strip()))
```

### Via device identification
There are functions to scan for available devices:

| Interface  | Function                                                       |
| :----------| :------------------------------------------------------------- |
| USB        | `EnumerateUSB(mask='')`                                        |
| TCP/IP     | `EnumerateTCPIPDevices(mask='')`                               |

Use `mask` (= string) to limit the number of devices to be found. If it is contained in the device identification, the device is found (see `qIDN`).
``` python
from pipython import GCSDevice
with GCSDevice() as pidevice:
    devices = pidevice.EnumerateTCPIPDevices(mask='C-884.4DB')
    for i, device in enumerate(devices):
        print('{} - {}'.format(i, device))
    item = int(input('Select device to connect:'))
    pidevice.ConnectTCPIPByDescription(devices[item])
    print('connected: {}'.format(pidevice.qIDN().strip()))
```

### Via dedicated interface
You can connect to a device via the following interfaces using the corresponding methods:

| Interface  | Method                                                         |
| :----------| :------------------------------------------------------------- |
| RS-232     | `ConnectRS232(comport, baudrate)`                              |
| USB        | `ConnectUSB(serialnum)`<br />`serialnum` = the serial number of the device as a string or the device identification returned by the `EnumerateUSB` method                          |
| USB via virtual COM port |  `ConnectRS232(comport, baudrate)` |
| TCP/IP     | `ConnectTCPIP(ipaddress, ipport=50000)`                        |
| TCP/IP     | `ConnectTCPIPByDescription(description)`<br />`description` = string returned by the `EnumerateTCPIPDevices` method                                                |
| NI GPIB    | `ConnectNIgpib(board, device)`                                 |
| PCI board  | `ConnectPciBoard(board)`                                       |

> **USB connection on Windows**
> 
> All PI USB controllers support a native USB connection using the `ConnectUSB()` function.<br/>
> Some PI USB controllers support also a connection via a virtual COM port using the `ConnectRS232()` function.
> 

> **USB connection on Linux**
> 
> Some PI USB controllers only support a native USB connection using the `ConnectUSB()` function.<br/>
> For information about which controllers only support a native USB connection, call the `EnumerateUSB()` function.
> Some PI USB controllers only support a connection via a virtual COM port using the `ConnectRS232()` function (for example, with `/dev/ttyUSB0` as `comport`). The `EnumerateUSB()` function does **not** return these controllers.
> 

``` python hl_lines="3"
from pipython import GCSDevice
with GCSDevice() as pidevice:
    pidevice.ConnectUSB(serialnum = '123456789')
    print('connected: {}'.format(pidevice.qIDN().strip()))
```


## Connecting to devices in a daisy chain network
Open the interface to the daisy chain master device (i.e., the device connected to the PC), then connect all devices of the daisy chain network to this interface.

> In a daisy chain network, each device must have a unique address (= device ID). There must be one device with the address 1, though this device does not have to be the master device (see controller manual on how to change the address of a device).
> 

Use the following methods to connect to the master device in a daisy chain network:

| Interface  | Method                                                         |
| :----------| :------------------------------------------------------------- |
| RS-232     | `OpenRS232DaisyChain(comport, baudrate)`                       |
| USB        | `OpenUSBDaisyChain(serialnum)`                                 |
| TCP/IP     | `OpenTCPIPDaisyChain(ipaddress, ipport=50000)`                 |

In the following example, three controllers are connected:

- C-863 controller as the master device, address 3
- E-861 controller, address 7
- C-867 controller, address 1
``` python hl_lines="3"
from pipython import GCSDevice
with GCSDevice() as c863:
    c863.OpenRS232DaisyChain(comport=1, baudrate=115200)
    # c863.OpenUSBDaisyChain(description='1234567890')
    # c863.OpenTCPIPDaisyChain(ipaddress='192.168.178.42')
    daisychainid = c863.dcid
    c863.ConnectDaisyChainDevice(3, daisychainid)
    with GCSDevice() as e861:
        e861.ConnectDaisyChainDevice(7, daisychainid)
        with GCSDevice() as c867:
            c867.ConnectDaisyChainDevice(1, daisychainid)
            print('\n{}:\n{}'.format(c863.GetInterfaceDescription(), c863.qIDN()))
            print('\n{}:\n{}'.format(e861.GetInterfaceDescription(), e861.qIDN()))
            print('\n{}:\n{}'.format(c867.GetInterfaceDescription(), c867.qIDN()))
```


## Connecting via low-level interface
The preferred method to connect to devices is `GCSDevice` using the GCS DLL. On platforms where the GCS DLL is not available, low-level functions of the PIPython package can be used instead.

| Interface  | Method                                                         |
| :----------| :------------------------------------------------------------- |
| RS-232     | `PISerial(comport, baudrate)`                                  |
| USB        | `PIUSB(serialnum)`                                             |
| USB via virtual COM port        | `PISerial(virtual_comport, baudrate)`                                             |
| TCP/IP     | `PISocket(ipaddress, ipport=50000)`                            |

> **USB connection on Windows**
> 
> A USB connection using `PIUSB()` is **not** supported on Windows.<br />
> Some PI USB controllers support a connection via a virtual COM port using the `PISerial()` function. Only these controllers are supported on Windows.
> 

> **USB connection on Linux**
> 
> Some PI USB controllers only support a native USB connection using `PIUSB()` and some PI USB controllers only support a connection via a virtual COM port using `PISerial()` (e.g., with `/dev/ttyUSB0` as `comport`).<br />
> If one method does not work, please try the other method.
> 


### PISerial

- Windows
``` python hl_lines="4"
from pipython.pidevice.gcscommands import GCSCommands
from pipython.pidevice.gcsmessages import GCSMessages
from pipython.pidevice.interfaces.piserial import PISerial
with PISerial(port='COM1', baudrate=115200) as gateway:
    messages = GCSMessages(gateway)
    with GCSCommands(messages) as pidevice:
        print(pidevice.qIDN())
```

- Linux
``` python hl_lines="4"
from pipython.pidevice.gcscommands import GCSCommands
from pipython.pidevice.gcsmessages import GCSMessages
from pipython.pidevice.interfaces.piserial import PISerial
with PISerial(port='/dev/ttyS0', baudrate=115200) as gateway:
    messages = GCSMessages(gateway)
    with GCSCommands(messages) as pidevice:
        print(pidevice.qIDN())
```

### PIUSB

- Windows: `PIUSB()` is **not** supported on Windows.
- Linux:
``` python hl_lines="4"
from pipython.pidevice.gcscommands import GCSCommands
from pipython.pidevice.gcsmessages import GCSMessages
from pipython.pidevice.interfaces.piusb import PIUSB
with PIUSB() as gateway:
    gateway.connect(serialnumber='1234567890', pid=0x1234)
    messages = GCSMessages(gateway)
    with GCSCommands(messages) as pidevice:
        print(pidevice.qIDN())
```

### PISocket
``` python hl_lines="4"
from pipython.pidevice.gcscommands import GCSCommands
from pipython.pidevice.gcsmessages import GCSMessages
from pipython.pidevice.interfaces.pisocket import PISocket
with PISocket(host='192.168.178.42', port=50000) as gateway:
    messages = GCSMessages(gateway)
    with GCSCommands(messages) as pidevice:
        print(pidevice.qIDN())
```


## Connecting to unknown devices
If `GCSDevice` is called with the controller name, the corresponding GCS DLL is chosen automatically. For unknown devices, a dedicated GCS DLL can be used instead.
``` python
from pipython import GCSDevice
with GCSDevice(gcsdll='PI_GCS2_DLL.dll') as pidevice:
	pidevice.InterfaceSetupDlg()
```

# Feature Version History

### PIPython 2.12.0
- add GCSCommands.LNK_USR()
- add GCSCommands.qLNK_USER()
- add GCSCommands.LNK_UDEL()
- add GCSCommands.qLNK_PRE()
- add GCSCommands.TRG_ENABLE()
- add GCSCommands.TRG_DISABLE()
- add GCSCommands.qTRG_STATE()

### PIPython 2.11.0
- add GCSCommands.FDL()

### PIPython 2.10.1
- fix 'segmentation fault' which occurred on Linux while unloading the GCSDll class

### PIPython 2.10.0
- add GCSCommands.STF()
- add new status bits returned by GCSCommands.STV().

### PIPython 2.9.0
- add support for open loop control mode in pitools
- fix bug in simplemove sample.

### PIPython 2.8.0
- add pitools.getmaxtravelrange()
- add pitools.getmintravelrange()

### PIPython 2.7.0
- add GCSCommands.qIPR()
- add GCSCommands.RES()
- add PILogger

### PIPython 2.6.0
- add support for GCS 3.0 controllers
- add data recorder tools for controllers with GCS 3.0 syntax 

### PIPython 2.5.1
- fix missing files in pitools

### PIPython 2.5.0
- Support for GCS30 UMF Controllers

### PIPython 2.4.0
- add GCSCommands.qUSG()
- add GCSCommands.SPV()
- add GCSCommands.qSPV()
- add GCSCommands.CPA()
- add GCSCommands.UCL()
- add GCSCommands.qUCL()
- add GCSCommands.REC_STAT()
- add GCSCommands.qREC_STAT()
- add GCSCommands.REC_TRACE()
- add GCSCommands.qREC_TRACE()
- add GCSCommands.REC_TRG()
- add GCSCommands.qREC_TRG()
- add GCSCommands.REC_RATE()
- add GCSCommands.qREC_RATE()
- add GCSCommands.REC_START()
- add GCSCommands.REC_STOP()
- add GCSCommands.qREC_NUM()
- add GCSCommands.qREC_DAT()
- add GCSCommands.qLOG()

### PIPython 2.3.0
- Internal refactoring

### PIPython 2.2.2
- fix: No module named gcs30.gcs30commands_helpers

### PIPython 2.2.1
- fix missing argument in isgcs30

### PIPython 2.2.0
- Support for PI_SetConnectTimeout() and PI_EnableBaudRateScan()

### PIPython 2.1.1
- fix timing problems while reading the data recorder with python3

### PIPython 2.1.0
- pipython.datarectools.Datarecorder: maxnumvalues now also reads the maximum number 
  of data recorder points from the 'HDR?' answer.
- fix pipython.pitools.pitoopls.itemstostr. If 'data' is an integer of 0 or a float of 0.0 
  'itemstostr' now returns the string '0' or '0.0' instead of 'None'

### PIPython 2.0.0
- New package structure
- support for WriteConfigurationFromDatabaseToControllerAndSave()

### PIPython 1.5.2
- Linux: fix string decoding in piusb

### PIPython 1.5.1
- fix parameter value conversion of hex parameter values

### PIPython 1.5.0
- add GCSCommands.POL()
- add GCSCommands.STD()
- add GCSCommands.RTD()
- add GCSCommands.qRTD()
- add GCSCommands.qLST()
- add GCSCommands.DTL()

### PIPython 1.4.0
- fix string decoding in GCSDll()
- add pitools.getservo()
- pitools.waitonreferencing() does not call waitontarget()
- in pitools call waitonready() with the "polldelay" argument
- fix signature of GCSCommands.qTWS()
- GCSCommands.CCL() will reset the list of supported GCS commands
- interfaces.pisocket.PISocket() uses socket.TCP_NODELAY
- add "ATZ" as "referencing command" to pitools.DeviceStartup()
- add GCSMessages.logfile property
- rename license.md to eula.md
- add datarectools.get_hdr_options()
- add Datarecorder.recopts property
- add Datarecorder.trigopts property
- all timeout default values are set to 300 seconds

### PIPython 1.3.9
- add pitools.waitonmacro()
- catch GCS error 2 (unknown command) after EAX during startup
- GCS commands arguments can be sets, too
- DDL(tables, offsets, values) -> DDL(table, offsets, values)
- add GCSDevice.isavailable
- convert parameter values according to types in qHPA answer
- fix signature of GCSCommands.qJLT()

### PIPython 1.3.8
- add interfaces.piusb
- add pitools.readgcsarray()
- add pitools.waitonwavegen()
- add pitools.moveandwait()
- add piparams.applyconfig()
- pitools.startup() defines and references stages only if necessary
- add GCSCommands.allaxes
- add GCSDevice.hasref()
- add GCSDevice.haslim()
- add GCSDevice.canfrf()
- add GCSDevice.canfnl()
- add GCSDevice.canfpl()
- add pitools.waitonphase()
- add pitools.setservo()
- controller specific startup sequence

### PIPython 1.3.7
- add pitools.movetomiddle()
- add pipython.fastaligntools
- PI_GCS2_DLL is used by default
- add pitools.savegcsarray()
- add pitools.itemstostr()

### PIPython 1.3.6
- add controller C-886, E-872
- GCSDevice supports external Gateway

### PIPython 1.3.5
- add DLL functions for PIStages3
- "wait on" functions support polldelay times
- fix GCSCommands.SGA()
- fix GCSCommands.qSPA()
- fix GCSCommands.qSEP()
- add optional argument "noraise" for StopAll(), HLT(), STP()
- add pitools.waitonfastalign()
- add pitools.waitonautozero()
- add GCS Error codes

### PIPython 1.3.4
- add pipython.interfaces.piserial
- "wait on" functions support predelay and postdelay times
- add GCSCommands.TSP()
- setup writes key for PIUpdateFinder always into 32 bit part of registry
- change formatting of numbers in GCS strings
- GCSDll supports "K" devices
- GCSMessages.bufstate will not write to log
- rename ReadGCSData() -> read_gcsdata()
- add controller C-663.12
- add parameters for E-873.3QTU, C-663.10C885
- add GCS Error codes
- bugfix

### PIPython 1.3.3
- add GCSCommands.SGP()
- add GCSCommands.qSGP()
- add GCSCommands.WAV_SIN()
- add GCSCommands.WAV_POL()
- add GCSCommands.WAV_TAN()
- add GCSCommands.WAV_SWEEP()
- add GCSCommands.checkerror()
- add GCSCommands.DEL()
- add new controllers
- add controller parameters
- fix for handling Unicode in Python 3
- bugfix of some GCS commands

### PIPython 1.3.2
- add GCSCommands.FSF()
- add GCSCommands.qFSF()
- add GCSCommands.qFSR()
- add pitools.getaxeslist()
- add pitools.ontarget()
- add pitools.waitonwalk()
- add pitools.waitonoma()
- add pitools.waitontrajectory()
- fix DLL function prefix

