Add python GUI WIP
This commit is contained in:
449
README.md
449
README.md
@@ -1,453 +1,10 @@
|
||||
e3-ecmc_plugin_fft
|
||||
ecmc_plugin_motion
|
||||
======
|
||||
ESS Site-specific EPICS module : ecmcPlugin_FFT
|
||||
Plugin designed for commisioning and troubleshooting of motion axes.
|
||||
Motion data are sampled, buffered and exposed to epics as waveforms.
|
||||
|
||||
A shared library with FFT functionalities loadable into ecmc:
|
||||
|
||||
https://github.com/epics-modules/ecmc (or local ess fork https://github.com/icshwi/ecmc).
|
||||
|
||||
Configuration is made through ecmccfg:
|
||||
|
||||
https://github.com/paulscherrerinstitute/ecmccfg (ot local ess fork https://github.com/icshwi/ecmccfg)
|
||||
|
||||
FFT:s are calculated with the kissfft lib:
|
||||
https://github.com/mborgerding/kissfft
|
||||
|
||||
|
||||
# Introduction
|
||||
|
||||
The main functionality of this plugin is to make FFT analysis of ecmc data. Most data in ecmc is accessible:
|
||||
1. EtherCAT data ("ec0.s1.AI_1" or "ec0.s2.mm.CH1_ARRAY")
|
||||
2. Motion data ("ax1.actpos")
|
||||
3. PLC data ("plcs.plc0.static.testvalue")
|
||||
|
||||
Precautions have been taken in order to disturb ecmc real time thread as little as possible:
|
||||
1. All CPU intensive calculations are handled in a low prio work thread (not ecmc thread).
|
||||
2. Communication to epics is made via a dedicated asynPortDriver (not the same that ecmc uses).
|
||||
|
||||
See below for configuration options and the different modes supported by this ecmc plugin.
|
||||
|
||||
## Loading of FFT plugin in ecmc:
|
||||
A plugin is loaded by the ecmccfg command loadPlugin:
|
||||
https://github.com/icshwi/ecmccfg/blob/master/scripts/loadPlugin.cmd
|
||||
|
||||
Example:
|
||||
```
|
||||
${SCRIPTEXEC} ${ecmccfg_DIR}loadPlugin.cmd, "PLUGIN_ID=0,FILE=libecmcPlugin_FFT.so,CONFIG='DBG_PRINT=1;SOURCE=ax1.actpos;RM_LIN=1;', REPORT=1
|
||||
dbLoadRecords(ecmcPluginFFT.template,"P=$(IOC):,INDEX=0, NELM=${FFT_NELM}")
|
||||
```
|
||||
This plugin supports multiple loading. For each load of the plugin a new FFT object will be created. In order to access these plugins, from plc:s or EPICS records, they can be accessed by an index. The first FFT plugin will have index 0. The next loaded FFT plugin will have index 1...
|
||||
|
||||
Note: If another plugin is loaded in between the loading of FFT plugins, it will have no affect on these FFT indexes (so the FFT index is _not_ the same as plugin index).
|
||||
|
||||
## Configuration
|
||||
|
||||
The different available configuration settings:
|
||||
* SOURCE= source variable : Sets source variable for FFT (example: ec0.s1.AI_1). This config is mandatory.
|
||||
* DBG_PRINT=1/0 : Enables/disables printouts from plugin, default = disabled.
|
||||
* NFFT= nfft : Data points to collect, default = 4096.
|
||||
* SCALE=scale : Apply scale to input data, default = 1.0.
|
||||
* RM_DC=1/0 : Remove DC offset of input data (SOURCE), default = disabled.
|
||||
* RM_LIN=1/0 : Remove linear component input data (SOURCE), default = disabled.
|
||||
* ENABLE=1/0 : Enable data acq. and calcs (can be controlled over asyn), default = disabled.
|
||||
* MODE=CONT/TRIGG : Continious or triggered mode, defaults to TRIGG
|
||||
* RATE=rate in hz : fft data sample rate in hz (must be lower than ecmc rate and (ecmc_rate/fft_rate)=integer), default = ecmc rate.
|
||||
* BREAKTABLE= EPICS breaktable : Apply breaktable to raw value.
|
||||
|
||||
Example configuration string:
|
||||
```
|
||||
"SOURCE=ax1.poserr;MODE=TRIGG;DBG_PRINT=1;ENABLE=1;"
|
||||
```
|
||||
|
||||
#### SOURCE (mandatory)
|
||||
The data source is defined by setting the SOURCE option in the plugin configuration string.
|
||||
This configuration is mandatory.
|
||||
|
||||
Example: Axis 1 actpos (See RM_LIN config below)
|
||||
```
|
||||
"DBG_PRINT=1;SOURCE=ax1.actpos;RM_LIN=1;"
|
||||
|
||||
```
|
||||
Example: Ethercat slave 1 analog input ch1
|
||||
```
|
||||
"DBG_PRINT=1;SOURCE=ec0.s1.AI_1;"
|
||||
```
|
||||
#### DBG_PRINT (default: disabled)
|
||||
Enable/disable printouts from plugin can be made bu setting the "DBG_PRINT" option.
|
||||
|
||||
Example: Disable
|
||||
```
|
||||
"DBG_PRINT=0;SOURCE=ax1.poserr;"
|
||||
```
|
||||
|
||||
#### NFFT (default: 4096)
|
||||
Defines number of samples for each measurement.
|
||||
|
||||
Note: Must be a n² number..
|
||||
|
||||
Example: 1024
|
||||
```
|
||||
"NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
|
||||
```
|
||||
#### SCALE (default 1.0)
|
||||
Apply custom scale to input data.
|
||||
|
||||
Example: 5.0
|
||||
```
|
||||
"SCALE=5.0;NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
|
||||
```
|
||||
### RM_DC
|
||||
Remove DC of input signal. Default is disabled.
|
||||
|
||||
Example: Remove DC offset
|
||||
```
|
||||
"RM_DC=1;SCALE=1;NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
|
||||
|
||||
```
|
||||
### RM_LIN
|
||||
Remove linear component of input signal. Default is disabled.
|
||||
The linear component is calculated by least square method.
|
||||
Could be usefull for values that increase, like actual position.
|
||||
|
||||
Example: Remove linear component
|
||||
```
|
||||
"RM_LIN=1;SCALE=1;NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
|
||||
```
|
||||
#### ENABLE (default: disabled)
|
||||
Enable data acq. and FFT calcs. The default settings is disabled so needs to be enabled from plc or over asyn in order to start calculations.
|
||||
|
||||
Example: Enable at startup by config
|
||||
```
|
||||
"ENABLE=1;RM_DC=1;SCALE=1;NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
|
||||
```
|
||||
Example: Enable FFT index 0 from EPICS:
|
||||
```
|
||||
caput IOC_TEST:Plugin-FFT0-Enable 1
|
||||
```
|
||||
Example: Enable FFT index 0 from ecmc PLC code:
|
||||
```
|
||||
fft_enable(0,1)
|
||||
```
|
||||
|
||||
#### MODE (default: TRIGG)
|
||||
The FFT module can operate in two different modes:
|
||||
* CONTINIOUS (CONT)
|
||||
* TRIGGERED (TRIGG)
|
||||
|
||||
Continious mode:
|
||||
1. Clear data buffers
|
||||
2. Wait for enable (from plc or asyn/epics record or configuration(see above))
|
||||
3. Data acqusition starts
|
||||
4. When buffers are full a worker thread will be triggered to calculate the FFT.
|
||||
5. FTT results are sent by callback over asyn to epics records
|
||||
6. goto 1.
|
||||
|
||||
Note: data will be "lost" during calculation time.
|
||||
|
||||
Triggered mode:
|
||||
1. Clear data buffers
|
||||
2. Wait for enable (from plc or asyn/epics record or configuration(see above))
|
||||
3. Wait for trigger (from plc or asyn/epics record)
|
||||
3. Data acqusition starts
|
||||
4. When buffers are full a worker thread will be triggered to calculate the FFT.
|
||||
5. FTT results are sent by callback over asyn to epics records
|
||||
6. goto 1.
|
||||
|
||||
Example: Mode triggered
|
||||
```
|
||||
"MODE=TRIGG;ENABLE=1;RM_DC=1;SCALE=1;NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
|
||||
```
|
||||
Example: Mode from EPICS record
|
||||
```
|
||||
# CONT
|
||||
caput IOC_TEST:Plugin-FFT0-Mode-RB 1
|
||||
# TRIGG
|
||||
caput IOC_TEST:Plugin-FFT0-Mode-RB 2
|
||||
```
|
||||
Note: The record is a output record with readback so can both be read and written to.
|
||||
|
||||
#### RATE (default: the ecmc rate of the selected data source)
|
||||
Sets the sample rate of the raw input data (from data source). The default value is the ecmc rate for that data source.
|
||||
Note: only a lower and "integer" division of sample rate can be defined.
|
||||
|
||||
Example: Rate = 100Hz
|
||||
```
|
||||
"RATE=100;MODE=TRIGG;ENABLE=1;RM_DC=1;SCALE=1;NFFT=1024;DBG_PRINT=0;SOURCE=ax1.poserr;"
|
||||
```
|
||||
|
||||
#### BREAKTABLE (default: no break table)
|
||||
|
||||
Note: The break table must be added in EPICS
|
||||
|
||||
Example: Apply BREAKTABLE=typeTelemess33020_21877 to raw value
|
||||
```
|
||||
"...;BREAKTABLE=typeTelemess33020_21877;"
|
||||
```
|
||||
|
||||
Example: Load custom breaktable in EPICS
|
||||
```
|
||||
dbLoadRecords ./bptBreakTable.dbd
|
||||
```
|
||||
|
||||
Example: breaktable (in a bptBreakTable.dbd)
|
||||
```
|
||||
# <raw> <eng>
|
||||
breaktable(typeTelemess33020_21877) {
|
||||
2502.48 0
|
||||
4058.89 1.5
|
||||
6164.64 3
|
||||
8880.74 4.5
|
||||
11993.57 6
|
||||
15259.00 7.5
|
||||
18371.84 9
|
||||
21271.05 10.5
|
||||
24658.54 12
|
||||
27344.13 13.5
|
||||
29297.28 15
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## EPICS records
|
||||
Each FFT plugin object will create a new asynportdriver-port named "PLUGIN.FFT<index>" (index is explaine above).
|
||||
The reason for a dedicated asynport is to disturb ecmc as little as possible.
|
||||
The plugin contains a template file, "ecmcPluginFFT.template", that will make most information availbe from records:
|
||||
* Rawdata array (ro)
|
||||
* FFT amplitude array (result) (ro)
|
||||
* FFT x axis (frequencies) (ro)
|
||||
* Status (ro)
|
||||
* Mode (rw)
|
||||
* Sample rate (ro)
|
||||
* Enable cmd (rw)
|
||||
* Trigger cmd (rw)
|
||||
* NFFT (ro)
|
||||
|
||||
The available records from this template file can be listed by the cmd (here two FFT plugins loaded):
|
||||
```
|
||||
dbgrep *FFT*
|
||||
IOC_TEST:Plugin-FFT0-SampleRate-Act
|
||||
IOC_TEST:Plugin-FFT1-SampleRate-Act
|
||||
IOC_TEST:Plugin-FFT0-Mode-RB
|
||||
IOC_TEST:Plugin-FFT1-Mode-RB
|
||||
IOC_TEST:Plugin-FFT0-Source
|
||||
IOC_TEST:Plugin-FFT0-Raw-Data-Act
|
||||
IOC_TEST:Plugin-FFT0-Spectrum-Amp-Act
|
||||
IOC_TEST:Plugin-FFT0-Spectrum-X-Axis-Act
|
||||
IOC_TEST:Plugin-FFT1-Source
|
||||
IOC_TEST:Plugin-FFT1-Raw-Data-Act
|
||||
IOC_TEST:Plugin-FFT1-Spectrum-Amp-Act
|
||||
IOC_TEST:Plugin-FFT1-Spectrum-X-Axis-Act
|
||||
IOC_TEST:Plugin-FFT0-Enable
|
||||
IOC_TEST:Plugin-FFT0-Trigg
|
||||
IOC_TEST:Plugin-FFT1-Enable
|
||||
IOC_TEST:Plugin-FFT1-Trigg
|
||||
IOC_TEST:Plugin-FFT0-stat
|
||||
IOC_TEST:Plugin-FFT0-NFFT
|
||||
IOC_TEST:Plugin-FFT1-stat
|
||||
IOC_TEST:Plugin-FFT1-NFFT
|
||||
```
|
||||
Note: The FFT asynparameters will not be visible by the ecmcReport iocsh command since the FFT records belong to another port.
|
||||
|
||||
## PLC interface
|
||||
|
||||
### PLC Functions
|
||||
|
||||
1. "fft_clear(arg0);" double fft_clear(index) : Clear/resets all buffers fft[index].
|
||||
2. "fft_enable(arg0, arg1);" double fft_enable(index, enable) : Set enable for fft[index].
|
||||
3. "fft_trigg(arg0);" double fft_trigg(index) : Trigg new measurement for fft[index]. Will clear buffers.
|
||||
4. "fft_mode(arg0, arg1);" double fft_mode(index, mode) : Set mode Cont(1)/Trigg(2) for fft[index].
|
||||
5. "fft_stat(arg0);" double fft_stat(index) : Get status of fft (NO_STAT, IDLE, ACQ, CALC) for fft[index].
|
||||
|
||||
### PLC Constants:
|
||||
|
||||
These constants are for use togheter with the above listed PLC functions:
|
||||
1. "fft_CONT" = 1: FFT Mode: Continious
|
||||
2. "fft_TRIGG" = 2: FFT Mode :Triggered
|
||||
3. "fft_NO_STAT" = 0: FFT Status: Invalid state
|
||||
4. "fft_IDLE" = 1: FFT Status: Idle state (waiting for trigger)
|
||||
5. "fft_ACQ" = 2: FFT Status: Acquiring data
|
||||
6. "fft_CALC" = 3: FFT Status: Calculating result
|
||||
|
||||
## Example script
|
||||
An example script can be found in the iocsh directory of this repo.
|
||||
This example load 2 FFT plugin objects:
|
||||
1. SOURCE="plcs.plc0.static.sineval" which is a plc variable updated as a sinus with default freq 5 Hz.
|
||||
2. SOURCE="ecmc.thread.latency.max". This is the execution latency of ecmc (in nanoseconds).
|
||||
|
||||
Start one FFT calc over EPICS records:
|
||||
```
|
||||
# Enable
|
||||
caput IOC_TEST:Plugin-FFT0-Enable 1
|
||||
# Trigg a new calc
|
||||
caput IOC_TEST:Plugin-FFT1-Trigg 1
|
||||
```
|
||||
|
||||
#### Example input signal
|
||||
Epics record: IOC_TEST:Plugin-FFT0-Raw-Data-Act
|
||||
|
||||
||
|
||||
| :---: |
|
||||
|**Figure 1** Raw data. |
|
||||
|
||||
#### Example FFT result
|
||||
Epics records:
|
||||
* X = IOC_TEST:Plugin-FFT0-Spectrum-X-Axis-Act
|
||||
* Y = IOC_TEST:Plugin-FFT0-Spectrum-Amp-Act
|
||||
|
||||
||
|
||||
| :---: |
|
||||
|**Figure 2** Resulting FFT amplitude. |
|
||||
|
||||
#### ecmc PLC code for example:
|
||||
```
|
||||
static.time:=ec_get_time()/1E9;
|
||||
static.sineval:=sin(2*pi*${FREQ=5}*static.time);
|
||||
```
|
||||
|
||||
## FFT GUI
|
||||
|
||||
### FFT GUI (FFT and rawdata plots + controls)
|
||||
A simple tool, [ecmcFFTMainGui.py](tools/ecmcFFTMainGui.py), to visualize the calculated spectrum, rawdata and also plugin controls can be found in the tools directory. The GUI connects to the plugin records over pypics framwork.
|
||||
The gui are included in the ecmccomgui repo:
|
||||
https://github.com/anderssandstrom/ecmccomgui
|
||||
|
||||
Example: ecmcFFTMainGui.py help printout
|
||||
```
|
||||
python ecmcFFTMainGui.py
|
||||
ecmcFFTMainGui: Plots waveforms of FFT data (updates on Y data callback).
|
||||
python ecmcFFTMainGui.py <prefix> <fftId>
|
||||
<prefix>: Ioc prefix ('IOC_TEST:')
|
||||
<fftId> : Id of fft plugin ('0')
|
||||
example : python ecmcFFTMainGui.py 'IOC_TEST:' '0'
|
||||
Will connect to Pvs: <prefix>Plugin-FFT<fftId>-*
|
||||
```
|
||||
|
||||
Example: Start ecmcFFMainTGui.py for:
|
||||
* predix="IOC_TEST:"
|
||||
* fftPluginId=0 (the first loaded FFT plugin in the ioc)
|
||||
```
|
||||
python ecmcFFTMainGui.py IOC_TEST: 0
|
||||
```
|
||||

|
||||
|
||||
### FFT GUI (only FFT plot)
|
||||
|
||||
A simple tool, [ecmcFFTGui.py](tools/ecmcFFTGui.py), to visualize the calculated spectrum can be found in the tools directory.
|
||||
The GUI connects to the plugin records over pypics framwork.
|
||||
|
||||
Example: ecmcFFTGui.py help printout
|
||||
```
|
||||
python ecmcFFTGui.py
|
||||
ecmcFFTGui: Plots waveforms of FFT data (updates on Y data callback).
|
||||
python ecmcFFTGui.py <x.pv> <y.pv>
|
||||
example: python ecmcFFTGui.py IOC_TEST:Plugin-FFT1-Spectrum-X-Axis-Act IOC_TEST:Plugin-FFT1-Spectrum-Amp-Act
|
||||
```
|
||||
|
||||
Example: Start ecmcFFTGui.py for spectrum amplitude and freq. waveform pvs
|
||||
```
|
||||
python ecmcFFTGui.py IOC_TEST:Plugin-FFT1-Spectrum-X-Axis-Act IOC_TEST:Plugin-FFT1-Spectrum-Amp-Act
|
||||
```
|
||||

|
||||
|
||||
### Array GUI (generic waveform plt)
|
||||
A simple generic tool, [ecmcFFTGui.py](tools/ecmcFFTGui.py), to visualize wavforms.
|
||||
The GUI connects to the plugin records over pypics framwork.
|
||||
|
||||
Example: ecmcArrayGui.py help printout
|
||||
```
|
||||
python ecmcArrayGui.py
|
||||
ecmcArrayGui: Plots waveforms data (updates on data callback).
|
||||
python ecmcArrayGui.py <y.pv>
|
||||
example: python ecmcArrayGui.py IOC_TEST:Plugin-FFT0-Raw-Data-Act
|
||||
```
|
||||
|
||||
Example: Start ecmcArrayGui.py for raw data wavform
|
||||
```
|
||||
python ecmcArrayGui.py IOC_TEST:Plugin-FFT0-Raw-Data-Act
|
||||
```
|
||||

|
||||
|
||||
### Needed packages:
|
||||
* python 3.5
|
||||
* epics
|
||||
* PyQt5
|
||||
* numpy
|
||||
* matplotlib
|
||||
|
||||
|
||||
## Plugin info
|
||||
|
||||
```
|
||||
Plugin info:
|
||||
Index = 1
|
||||
Name = ecmcPlugin_FFT
|
||||
Description = FFT plugin for use with ecmc.
|
||||
Option description =
|
||||
DBG_PRINT=<1/0> : Enables/disables printouts from plugin, default = disabled.
|
||||
SOURCE=<source> : Sets source variable for FFT (example: ec0.s1.AI_1).
|
||||
NFFT=<nfft> : Data points to collect, default = 4096.
|
||||
SCALE=<1/0> : Apply scale, default = disabled.
|
||||
RM_DC=<1/0> : Remove DC offset of input data (SOURCE), default = disabled.
|
||||
ENABLE=<1/0> : Enable data acq. and calcs (can be controlled over asyn), default = disabled.
|
||||
MODE=<CONT/TRIGG> : Continious or triggered mode, defaults to TRIGG
|
||||
RATE=<rate in hz> : fft data sample rate in hz (must be lower than ecmc rate and (ecmc_rate/fft_rate)=integer), default = ecmc rate.
|
||||
Filename = /epics/base-7.0.3.1/require/3.1.2/siteMods/ecmcPlugin_FFT/master/lib/linux-arm/libecmcPlugin_FFT.so
|
||||
Config string = SOURCE=ecmc.thread.latency.max;DBG_PRINT=0;NFFT=1024;
|
||||
Version = 1
|
||||
Interface version = 512 (ecmc = 512)
|
||||
max plc funcs = 64
|
||||
max plc func args = 10
|
||||
max plc consts = 64
|
||||
Construct func = @0xb5022044
|
||||
Enter realtime func = @0xb5022090
|
||||
Exit realtime func = @0xb502203c
|
||||
Realtime func = @0xb5022034
|
||||
Destruct func = @0xb502206c
|
||||
dlhandle = @0x1d9de28
|
||||
Plc functions:
|
||||
funcs[00]:
|
||||
Name = "fft_clear(arg0);"
|
||||
Desc = double fft_clear(index) : Clear/reset fft[index].
|
||||
Arg count = 1
|
||||
func = @0xb5022094
|
||||
funcs[01]:
|
||||
Name = "fft_enable(arg0, arg1);"
|
||||
Desc = double fft_enable(index, enable) : Set enable for fft[index].
|
||||
Arg count = 2
|
||||
func = @0xb50220b0
|
||||
funcs[02]:
|
||||
Name = "fft_trigg(arg0);"
|
||||
Desc = double fft_trigg(index) : Trigg new measurement for fft[index]. Will clear buffers.
|
||||
Arg count = 1
|
||||
func = @0xb50220d4
|
||||
funcs[03]:
|
||||
Name = "fft_mode(arg0, arg1);"
|
||||
Desc = double fft_mode(index, mode) : Set mode Cont(1)/Trigg(2) for fft[index].
|
||||
Arg count = 2
|
||||
func = @0xb50220f0
|
||||
funcs[04]:
|
||||
Name = "fft_stat(arg0);"
|
||||
Desc = double fft_stat(index) : Get status of fft (NO_STAT, IDLE, ACQ, CALC) for fft[index].
|
||||
Arg count = 1
|
||||
func = @0xb5022114
|
||||
Plc constants:
|
||||
consts[00]:
|
||||
Name = "fft_CONT" = 1.000
|
||||
Desc = FFT Mode: Continious
|
||||
consts[01]:
|
||||
Name = "fft_TRIGG" = 2.000
|
||||
Desc = FFT Mode :Triggered
|
||||
consts[02]:
|
||||
Name = "fft_NO_STAT" = 0.000
|
||||
Desc = FFT Status: Invalid state
|
||||
consts[03]:
|
||||
Name = "fft_IDLE" = 1.000
|
||||
Desc = FFT Status: Idle state (waiting for trigger)
|
||||
consts[04]:
|
||||
Name = "fft_ACQ" = 2.000
|
||||
Desc = FFT Status: Acquiring data
|
||||
consts[05]:
|
||||
Name = "fft_CALC" = 3.000
|
||||
Desc = FFT Status: Calculating result
|
||||
|
||||
```
|
||||
|
||||
@@ -28,6 +28,8 @@ IOC_TEST:Axis1-VelAct
|
||||
IOC_TEST:Axis1-PosSet
|
||||
IOC_TEST:Axis1-PosErr
|
||||
IOC_TEST:Axis1-PLC-Err
|
||||
IOC_TEST:Plg-Mtn0-BuffSze
|
||||
IOC_TEST:Plg-Mtn0-ElmCnt
|
||||
IOC_TEST:MCU-AppMode
|
||||
IOC_TEST:MCU-ErrId
|
||||
IOC_TEST:MCU-ThdLatMin
|
||||
@@ -170,12 +172,24 @@ IOC_TEST:m0-Dom-WC-Zero
|
||||
IOC_TEST:m0-Dom-WC-Incomplete
|
||||
IOC_TEST:m0-Dom-WC-Complete
|
||||
IOC_TEST:m0-Stat-OK
|
||||
REQMOD:raspberrypi-3352:exit
|
||||
REQMOD:raspberrypi-3352:MODULES
|
||||
REQMOD:raspberrypi-3352:VERSIONS
|
||||
REQMOD:raspberrypi-3352:MOD_VER
|
||||
REQMOD:raspberrypi-1801:exit
|
||||
REQMOD:raspberrypi-1801:MODULES
|
||||
REQMOD:raspberrypi-1801:VERSIONS
|
||||
REQMOD:raspberrypi-1801:MOD_VER
|
||||
IOC_TEST:Axis1-Arr-Stat
|
||||
IOC_TEST:Axis1-PLC-Expr-RB
|
||||
IOC_TEST:Plg-Mtn0-PosAct-Arr
|
||||
IOC_TEST:Plg-Mtn0-PosSet-Arr
|
||||
IOC_TEST:Plg-Mtn0-PosErr-Arr
|
||||
IOC_TEST:Plg-Mtn0-Time-Arr
|
||||
IOC_TEST:Plg-Mtn0-Ena-Arr
|
||||
IOC_TEST:Plg-Mtn0-EnaAct-Arr
|
||||
IOC_TEST:Plg-Mtn0-Bsy-Arr
|
||||
IOC_TEST:Plg-Mtn0-Exe-Arr
|
||||
IOC_TEST:Plg-Mtn0-TrjSrc-Arr
|
||||
IOC_TEST:Plg-Mtn0-EncSrc-Arr
|
||||
IOC_TEST:Plg-Mtn0-AtTrg-Arr
|
||||
IOC_TEST:Plg-Mtn0-ErrId-Arr
|
||||
IOC_TEST:MCU-ErrMsg
|
||||
IOC_TEST:MCU-Updated
|
||||
IOC_TEST:m0s001-Enc01-LtchCmd
|
||||
@@ -196,6 +210,8 @@ IOC_TEST:Axis1-DIR_
|
||||
IOC_TEST:Axis1-ErrRst
|
||||
IOC_TEST:Axis1-HomProc
|
||||
IOC_TEST:Axis1-MtnCmdData
|
||||
IOC_TEST:Plg-Mtn0-Mde-RB
|
||||
IOC_TEST:Plg-Mtn0-Cmd-RB
|
||||
IOC_TEST:m0s001-Stat
|
||||
IOC_TEST:m0s002-Stat
|
||||
IOC_TEST:Axis1-MR-ErrId
|
||||
@@ -206,6 +222,7 @@ IOC_TEST:Axis1-CfgDLLM-En-RB
|
||||
IOC_TEST:Axis1-Stat
|
||||
IOC_TEST:Axis1-ErrId
|
||||
IOC_TEST:Axis1-WrnId
|
||||
IOC_TEST:Plg-Mtn0-Stat
|
||||
IOC_TEST:m0-Stat
|
||||
IOC_TEST:m0-SlvCntr
|
||||
IOC_TEST:m0-MemmapCntr
|
||||
@@ -228,17 +245,18 @@ IOC_TEST:m0-SlvRsp
|
||||
IOC_TEST:m0-Dom-WC
|
||||
IOC_TEST:m0s001-Enc01-LtchRst
|
||||
IOC_TEST:Axis1-Cmd_
|
||||
REQMOD:raspberrypi-3352:BaseVersion
|
||||
REQMOD:raspberrypi-3352:require_VER
|
||||
REQMOD:raspberrypi-3352:ecmccfg_VER
|
||||
REQMOD:raspberrypi-3352:asyn_VER
|
||||
REQMOD:raspberrypi-3352:exprtk_VER
|
||||
REQMOD:raspberrypi-3352:motor_VER
|
||||
REQMOD:raspberrypi-3352:ruckig_VER
|
||||
REQMOD:raspberrypi-3352:ecmc_VER
|
||||
REQMOD:raspberrypi-1801:BaseVersion
|
||||
REQMOD:raspberrypi-1801:require_VER
|
||||
REQMOD:raspberrypi-1801:ecmccfg_VER
|
||||
REQMOD:raspberrypi-1801:asyn_VER
|
||||
REQMOD:raspberrypi-1801:exprtk_VER
|
||||
REQMOD:raspberrypi-1801:motor_VER
|
||||
REQMOD:raspberrypi-1801:ruckig_VER
|
||||
REQMOD:raspberrypi-1801:ecmc_VER
|
||||
IOC_TEST:m0s001-HWType
|
||||
IOC_TEST:m0s002-HWType
|
||||
IOC_TEST:Axis1-MsgTxt
|
||||
REQMOD:raspberrypi-1801:ecmc_plugin_motion_VER
|
||||
IOC_TEST:m0s001-Drv01-Stat
|
||||
IOC_TEST:m0s001-Enc01-Stat
|
||||
IOC_TEST:m0s001-Stat_
|
||||
@@ -287,6 +305,10 @@ IOC_TEST:Axis1-TgtVelCmd
|
||||
IOC_TEST:Axis1-Id
|
||||
IOC_TEST:MCU-Cfg-AX1-NxtObjId
|
||||
IOC_TEST:MCU-Cfg-AX-FrstObjId
|
||||
IOC_TEST:MCU-Cfg-PLG{Index}-NxtObjId
|
||||
IOC_TEST:MCU-Cfg-PLG-FrstObjId
|
||||
IOC_TEST:Plg-Mtn0-AxCmd-RB
|
||||
IOC_TEST:Plg-Mtn0-SmpHz-RB
|
||||
IOC_TEST:MCU-Cfg-Eng-Mode
|
||||
IOC_TEST:m0s001-Enc01-LchAutRstSp
|
||||
IOC_TEST:m0s002-BO01
|
||||
@@ -315,6 +337,8 @@ IOC_TEST:Axis1-PLC-EnaCmd
|
||||
IOC_TEST:Axis1-CmdFrmPLCCmd
|
||||
IOC_TEST:Axis1-SftLimBwdEna
|
||||
IOC_TEST:Axis1-SftLimFwdEna
|
||||
IOC_TEST:Plg-Mtn0-EnaCmd-RB
|
||||
IOC_TEST:Plg-Mtn0-TrgCmd-RB
|
||||
IOC_TEST:MCU-ErrRst
|
||||
IOC_TEST:Axis1-MCU1-asyn
|
||||
IOC_TEST:MCU-Cmd
|
||||
|
||||
827
tools/ecmcMotionMainGui.py
Normal file
827
tools/ecmcMotionMainGui.py
Normal file
@@ -0,0 +1,827 @@
|
||||
#*************************************************************************
|
||||
# Copyright (c) 2020 European Spallation Source ERIC
|
||||
# ecmc is distributed subject to a Software License Agreement found
|
||||
# in file LICENSE that is included with this distribution.
|
||||
#
|
||||
# ecmcMtnMainGui.py
|
||||
#
|
||||
# Created on: October 6, 2020
|
||||
# Author: Anders Sandström
|
||||
#
|
||||
# Plots two waveforms (x vs y) updates for each callback on the y-pv
|
||||
#
|
||||
#*************************************************************************
|
||||
|
||||
import sys
|
||||
import os
|
||||
import epics
|
||||
from PyQt5.QtWidgets import *
|
||||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtGui import *
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
matplotlib.use("Qt5Agg")
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.animation import TimedAnimation
|
||||
from matplotlib.lines import Line2D
|
||||
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
|
||||
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
|
||||
import matplotlib.pyplot as plt
|
||||
import threading
|
||||
|
||||
# List of pv names
|
||||
pvlist= [ 'BuffSze',
|
||||
'ElmCnt',
|
||||
'PosAct-Arr',
|
||||
'PosSet-Arr',
|
||||
'PosErr-Arr',
|
||||
'Time-Arr',
|
||||
'Ena-Arr',
|
||||
'EnaAct-Arr',
|
||||
'Bsy-Arr',
|
||||
'Exe-Arr',
|
||||
'TrjSrc-Arr',
|
||||
'EncSrc-Arr',
|
||||
'AtTrg-Arr',
|
||||
'ErrId-Arr',
|
||||
'Mde-RB',
|
||||
'Cmd-RB',
|
||||
'Stat',
|
||||
'AxCmd-RB',
|
||||
'SmpHz-RB',
|
||||
'TrgCmd-RB',
|
||||
'EnaCmd-RB' ]
|
||||
|
||||
pvmiddlestring='Plg-Mtn'
|
||||
|
||||
class comSignal(QObject):
|
||||
data_signal = pyqtSignal(object)
|
||||
|
||||
class ecmcMtnMainGui(QtWidgets.QDialog):
|
||||
def __init__(self,prefix=None,mtnPluginId=None):
|
||||
super(ecmcMtnMainGui, self).__init__()
|
||||
#Mtn plugin
|
||||
self.pvnames={}
|
||||
self.pvs={}
|
||||
self.pv_signal_cbs={}
|
||||
self.data={}
|
||||
|
||||
#
|
||||
|
||||
self.offline = False
|
||||
self.pvPrefixStr = prefix
|
||||
self.pvPrefixOrigStr = prefix # save for restore after open datafile
|
||||
self.mtnPluginId = mtnPluginId
|
||||
self.mtnPluginOrigId = mtnPluginId
|
||||
self.allowSave = False
|
||||
self.path = '.'
|
||||
self.unitRawY = "[]"
|
||||
self.unitSpectY = "[]"
|
||||
self.labelSpectY = "Amplitude"
|
||||
self.labelRawY = "Raw"
|
||||
self.title = ""
|
||||
self.NMtn = 1024
|
||||
self.sampleRate = 1000
|
||||
self.sampleRateValid = False
|
||||
self.MtnYDataValid = False
|
||||
self.MtnXDataValid = False
|
||||
self.RawYDataValid = False
|
||||
self.RawXDataValid = False
|
||||
if prefix is None or mtnPluginId is None:
|
||||
self.offline = True
|
||||
self.pause = True
|
||||
self.enable = False
|
||||
else:
|
||||
#Check for connection else go offline
|
||||
self.buildPvNames()
|
||||
connected = self.pvs['BuffSze'].wait_for_connection(timeout=2)
|
||||
if connected:
|
||||
self.offline = False
|
||||
self.pause = False
|
||||
else:
|
||||
self.offline = True
|
||||
self.pause = True
|
||||
self.enable = False
|
||||
|
||||
# Callbacks through signals
|
||||
#self.comSignalX = comSignal()
|
||||
#self.comSignalX.data_signal.connect(self.callbackFuncX)
|
||||
#
|
||||
#self.comSignalSpectY = comSignal()
|
||||
#self.comSignalSpectY.data_signal.connect(self.callbackFuncSpectY)
|
||||
#self.comSignalRawData = comSignal()
|
||||
#self.comSignalRawData.data_signal.connect(self.callbackFuncrawData)
|
||||
#self.comSignalEnable = comSignal()
|
||||
#self.comSignalEnable.data_signal.connect(self.callbackFuncEnable)
|
||||
#self.comSignalMode = comSignal()
|
||||
#self.comSignalMode.data_signal.connect(self.callbackFuncMode)
|
||||
#self.comSignalBuffIdAct = comSignal()
|
||||
#self.comSignalBuffIdAct.data_signal.connect(self.callbackFuncBuffIdAct)
|
||||
self.startupDone=False
|
||||
|
||||
self.pause = 0
|
||||
|
||||
# Data
|
||||
#self.dataX = None
|
||||
#self.spectY = None
|
||||
#self.rawdataY = None
|
||||
#self.rawdataX = None
|
||||
#self.enable = None
|
||||
|
||||
|
||||
self.createWidgets()
|
||||
#self.connectPvs()
|
||||
self.setStatusOfWidgets()
|
||||
self.resize(1000,850)
|
||||
|
||||
return
|
||||
|
||||
def createWidgets(self):
|
||||
|
||||
self.figure = plt.figure()
|
||||
self.plottedLineSpect = None
|
||||
self.plottedLineRaw = None
|
||||
self.axSpect = None
|
||||
self.axRaw = None
|
||||
self.canvas = FigureCanvas(self.figure)
|
||||
self.toolbar = NavigationToolbar(self.canvas, self)
|
||||
self.pauseBtn = QPushButton(text = 'pause')
|
||||
self.pauseBtn.setFixedSize(100, 50)
|
||||
self.pauseBtn.clicked.connect(self.pauseBtnAction)
|
||||
self.pauseBtn.setStyleSheet("background-color: green")
|
||||
self.openBtn = QPushButton(text = 'open data')
|
||||
self.openBtn.setFixedSize(100, 50)
|
||||
self.openBtn.clicked.connect(self.openBtnAction)
|
||||
self.saveBtn = QPushButton(text = 'save data')
|
||||
self.saveBtn.setFixedSize(100, 50)
|
||||
self.saveBtn.clicked.connect(self.saveBtnAction)
|
||||
self.enableBtn = QPushButton(text = 'enable Mtn')
|
||||
self.enableBtn.setFixedSize(100, 50)
|
||||
self.enableBtn.clicked.connect(self.enableBtnAction)
|
||||
self.triggBtn = QPushButton(text = 'trigg Mtn')
|
||||
self.triggBtn.setFixedSize(100, 50)
|
||||
self.triggBtn.clicked.connect(self.triggBtnAction)
|
||||
self.zoomBtn = QPushButton(text = 'auto zoom')
|
||||
self.zoomBtn.setFixedSize(100, 50)
|
||||
self.zoomBtn.clicked.connect(self.zoomBtnAction)
|
||||
self.modeCombo = QComboBox()
|
||||
self.modeCombo.setFixedSize(100, 50)
|
||||
self.modeCombo.currentIndexChanged.connect(self.newModeIndexChanged)
|
||||
self.modeCombo.addItem("CONT")
|
||||
self.modeCombo.addItem("TRIGG")
|
||||
self.progressBar = QProgressBar()
|
||||
self.progressBar.reset()
|
||||
self.progressBar.setMinimum(0)
|
||||
self.progressBar.setMaximum(100) #100%
|
||||
self.progressBar.setValue(0)
|
||||
self.progressBar.setFixedHeight(20)
|
||||
|
||||
# Fix layout
|
||||
self.setGeometry(300, 300, 900, 700)
|
||||
|
||||
layoutVert = QVBoxLayout()
|
||||
layoutVert.addWidget(self.toolbar)
|
||||
layoutVert.addWidget(self.canvas)
|
||||
|
||||
layoutControl = QHBoxLayout()
|
||||
layoutControl.addWidget(self.pauseBtn)
|
||||
layoutControl.addWidget(self.enableBtn)
|
||||
layoutControl.addWidget(self.triggBtn)
|
||||
layoutControl.addWidget(self.modeCombo)
|
||||
layoutControl.addWidget(self.zoomBtn)
|
||||
layoutControl.addWidget(self.saveBtn)
|
||||
layoutControl.addWidget(self.openBtn)
|
||||
|
||||
frameControl = QFrame(self)
|
||||
frameControl.setFixedHeight(70)
|
||||
frameControl.setLayout(layoutControl)
|
||||
|
||||
|
||||
layoutVert.addWidget(frameControl)
|
||||
layoutVert.addWidget(self.progressBar)
|
||||
self.setLayout(layoutVert)
|
||||
|
||||
def setStatusOfWidgets(self):
|
||||
self.saveBtn.setEnabled(self.allowSave)
|
||||
if self.offline:
|
||||
self.enableBtn.setStyleSheet("background-color: grey")
|
||||
self.enableBtn.setEnabled(False)
|
||||
self.pauseBtn.setStyleSheet("background-color: grey")
|
||||
self.pauseBtn.setEnabled(False)
|
||||
self.modeCombo.setEnabled(False)
|
||||
self.triggBtn.setEnabled(False)
|
||||
self.setWindowTitle("ecmc Mtn Main plot: Offline")
|
||||
else:
|
||||
self.modeCombo.setEnabled(True)
|
||||
# Check actual value of pvs
|
||||
enable = self.pvs['EnaCmd-RB'].get()
|
||||
if enable is None:
|
||||
print("pvs['EnaCmd-RB'].get() failed")
|
||||
return
|
||||
if(enable>0):
|
||||
self.enableBtn.setStyleSheet("background-color: green")
|
||||
self.enable = True
|
||||
else:
|
||||
self.enableBtn.setStyleSheet("background-color: red")
|
||||
self.enable = False
|
||||
|
||||
#self.sourceStr = self.pvSource.get(as_string=True)
|
||||
#if self.sourceStr is None:
|
||||
# print("pvSource.get() failed")
|
||||
# return
|
||||
|
||||
self.sampleRate = self.pvs['SmpHz-RB'].get()
|
||||
if self.sampleRate is None:
|
||||
print("pvs['SmpHz-RB'].get() failed")
|
||||
return
|
||||
self.sampleRateValid = True
|
||||
|
||||
self.mode = self.pvs['Mde-RB'].get()
|
||||
if self.mode is None:
|
||||
print("pvs['Mde-RB'].get() failed")
|
||||
return
|
||||
|
||||
self.modeStr = "NO_MODE"
|
||||
self.triggBtn.setEnabled(False) # Only enable if mode = TRIGG = 2
|
||||
if self.mode == 1:
|
||||
self.modeStr = "CONT"
|
||||
self.modeCombo.setCurrentIndex(self.mode-1) # Index starta t zero
|
||||
|
||||
if self.mode == 2:
|
||||
self.modeStr = "TRIGG"
|
||||
self.triggBtn.setEnabled(True)
|
||||
self.modeCombo.setCurrentIndex(self.mode-1) # Index starta t zero
|
||||
|
||||
self.setWindowTitle("ecmc Mtn Main plot: prefix=" + self.pvPrefixStr + " , mtnId=" + str(self.mtnPluginId) +
|
||||
", rate=" + str(self.sampleRate))
|
||||
|
||||
def buildPvNames(self):
|
||||
print('HEHEHEHHEH')
|
||||
# Pv names based on structure: <prefix>Plugin-Mtn<mtnPluginId>-<suffixname>
|
||||
for pv in pvlist:
|
||||
self.pvnames[pv]=self.buildPvName(pv)
|
||||
if self.pvnames[pv] is None:
|
||||
raise RuntimeError("pvname must not be 'None'")
|
||||
if len(self.pvnames[pv])==0:
|
||||
raise RuntimeError("pvname must not be ''")
|
||||
self.pvs[pv] = epics.PV(self.pvnames[pv])
|
||||
self.pv_signal_cbs[pv] = comSignal()
|
||||
|
||||
# Signal callbacks (update gui)
|
||||
# replace any '-' with '_' since '-' not allowed in funcion names
|
||||
sig_cb_func=getattr(self,'sig_cb_' + pv.replace('-','_'))
|
||||
self.pv_signal_cbs[pv].data_signal.connect(sig_cb_func)
|
||||
|
||||
# Pv monitor callbacks
|
||||
mon_cb_func=getattr(self,'on_change_' + pv.replace('-','_'))
|
||||
self.pvs[pv].add_callback(mon_cb_func)
|
||||
|
||||
QCoreApplication.processEvents()
|
||||
|
||||
def buildPvName(self, suffixname):
|
||||
return self.pvPrefixStr + pvmiddlestring + str(self.mtnPluginId) + '-' + suffixname
|
||||
|
||||
###### Pv monitor callbacks
|
||||
def on_change_BuffSze(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['BuffSze'].data_signal.emit(value)
|
||||
|
||||
def on_change_ElmCnt(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['ElmCnt'].data_signal.emit(value)
|
||||
|
||||
def on_change_PosAct_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['PosAct-Arr'].data_signal.emit(value)
|
||||
|
||||
def on_change_PosSet_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['PosSet-Arr'].data_signal.emit(value)
|
||||
|
||||
def on_change_PosErr_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['PosErr-Arr'].data_signal.emit(value)
|
||||
|
||||
def on_change_Time_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['Time-Arr'].data_signal.emit(value)
|
||||
|
||||
def on_change_Ena_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['Ena-Arr'].data_signal.emit(value)
|
||||
|
||||
def on_change_EnaAct_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['EnaAct-Arr'].data_signal.emit(value)
|
||||
|
||||
def on_change_Bsy_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['Bsy-Arr'].data_signal.emit(value)
|
||||
|
||||
def on_change_Exe_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['Exe-Arr'].data_signal.emit(value)
|
||||
|
||||
def on_change_TrjSrc_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['TrjSrc-Arr'].data_signal.emit(value)
|
||||
|
||||
def on_change_EncSrc_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['EncSrc-Arr'].data_signal.emit(value)
|
||||
|
||||
def on_change_AtTrg_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['AtTrg-Arr'].data_signal.emit(value)
|
||||
|
||||
def on_change_ErrId_Arr(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['ErrId-Arr'].data_signal.emit(value)
|
||||
|
||||
def on_change_Mde_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['Mde-RB'].data_signal.emit(value)
|
||||
|
||||
def on_change_Cmd_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['Cmd-RB'].data_signal.emit(value)
|
||||
|
||||
def on_change_Stat(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['Stat'].data_signal.emit(value)
|
||||
|
||||
def on_change_AxCmd_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['AxCmd-RB'].data_signal.emit(value)
|
||||
|
||||
def on_change_SmpHz_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['SmpHz-RB'].data_signal.emit(value)
|
||||
|
||||
def on_change_TrgCmd_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['TrgCmd-RB'].data_signal.emit(value)
|
||||
|
||||
def on_change_EnaCmd_RB(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
print('On Change')
|
||||
self.pv_signal_cbs['EnaCmd-RB'].data_signal.emit(value)
|
||||
|
||||
# def onChangePvMode(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
# if self.pause:
|
||||
# return
|
||||
# self.comSignalMode.data_signal.emit(value)
|
||||
#
|
||||
# def onChangePvEnable(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
# if self.pause:
|
||||
# return
|
||||
# self.comSignalEnable.data_signal.emit(value)
|
||||
#
|
||||
# def onChangeX(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
# if self.pause:
|
||||
# return
|
||||
# self.comSignalX.data_signal.emit(value)
|
||||
#
|
||||
# def onChangePvSpectY(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
# if self.pause:
|
||||
# return
|
||||
# self.comSignalSpectY.data_signal.emit(value)
|
||||
#
|
||||
# def onChangePvrawData(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
# if self.pause:
|
||||
# return
|
||||
# self.comSignalRawData.data_signal.emit(value)
|
||||
#
|
||||
# def onChangePvBuffIdAct(self,pvname=None, value=None, char_value=None,timestamp=None, **kw):
|
||||
# if self.pause:
|
||||
# return
|
||||
# self.comSignalBuffIdAct.data_signal.emit(value)
|
||||
|
||||
###### Signal callbacks
|
||||
# dbgrep *Plg*Mtn*
|
||||
# IOC_TEST:Plg-Mtn0-BuffSze
|
||||
# IOC_TEST:Plg-Mtn0-ElmCnt
|
||||
# IOC_TEST:Plg-Mtn0-PosAct-Arr
|
||||
# IOC_TEST:Plg-Mtn0-PosSet-Arr
|
||||
# IOC_TEST:Plg-Mtn0-PosErr-Arr
|
||||
# IOC_TEST:Plg-Mtn0-Time-Arr
|
||||
# IOC_TEST:Plg-Mtn0-Ena-Arr
|
||||
# IOC_TEST:Plg-Mtn0-EnaAct-Arr
|
||||
# IOC_TEST:Plg-Mtn0-Bsy-Arr
|
||||
# IOC_TEST:Plg-Mtn0-Exe-Arr
|
||||
# IOC_TEST:Plg-Mtn0-TrjSrc-Arr
|
||||
# IOC_TEST:Plg-Mtn0-EncSrc-Arr
|
||||
# IOC_TEST:Plg-Mtn0-AtTrg-Arr
|
||||
# IOC_TEST:Plg-Mtn0-ErrId-Arr
|
||||
# IOC_TEST:Plg-Mtn0-Mde-RB
|
||||
# IOC_TEST:Plg-Mtn0-Cmd-RB
|
||||
# IOC_TEST:Plg-Mtn0-Stat
|
||||
# IOC_TEST:Plg-Mtn0-AxCmd-RB
|
||||
# IOC_TEST:Plg-Mtn0-SmpHz-RB
|
||||
# IOC_TEST:Plg-Mtn0-TrgCmd-RB
|
||||
# IOC_TEST:Plg-Mtn0-EnaCmd-RB
|
||||
|
||||
def sig_cb_BuffSze(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_ElmCnt(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_PosAct_Arr(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_PosSet_Arr(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_PosErr_Arr(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_Time_Arr(self,value):
|
||||
if(np.size(value)) > 0:
|
||||
self.dataX = value
|
||||
self.MtnXDataValid = True
|
||||
return
|
||||
|
||||
|
||||
def sig_cb_Ena_Arr(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_EnaAct_Arr(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_Bsy_Arr(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_Exe_Arr(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_TrjSrc_Arr(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_EncSrc_Arr(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_AtTrg_Arr(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_ErrId_Arr(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_Mde_RB(self,value):
|
||||
|
||||
if value < 1 or value> 2:
|
||||
self.modeStr = "NO_MODE"
|
||||
print('callbackFuncMode: Error Invalid mode.')
|
||||
return
|
||||
|
||||
self.mode = value
|
||||
self.modeCombo.setCurrentIndex(self.mode-1) # Index starta t zero
|
||||
|
||||
if self.mode == 1:
|
||||
self.modeStr = "CONT"
|
||||
self.triggBtn.setEnabled(False) # Only enable if mode = TRIGG = 2
|
||||
|
||||
if self.mode == 2:
|
||||
self.modeStr = "TRIGG"
|
||||
self.triggBtn.setEnabled(True)
|
||||
|
||||
return
|
||||
|
||||
|
||||
def sig_cb_Cmd_RB(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_Stat(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_AxCmd_RB(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_SmpHz_RB(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_TrgCmd_RB(self,value):
|
||||
print('HEPP')
|
||||
|
||||
def sig_cb_EnaCmd_RB(self,value):
|
||||
self.enable = value
|
||||
if self.enable:
|
||||
self.enableBtn.setStyleSheet("background-color: green")
|
||||
else:
|
||||
self.enableBtn.setStyleSheet("background-color: red")
|
||||
self.data['EnaCmd-RB'] = value
|
||||
return
|
||||
|
||||
def callbackFuncSpectY(self, value):
|
||||
if(np.size(value)) > 0:
|
||||
self.spectY = value
|
||||
self.MtnYDataValid = self.RawXDataValid
|
||||
self.plotAll()
|
||||
return
|
||||
|
||||
def callbackFuncrawData(self, value):
|
||||
if(np.size(value)) > 0:
|
||||
if (self.rawdataX is None or np.size(value) != np.size(self.rawdataY)) and self.sampleRateValid:
|
||||
self.rawdataX = np.arange(-np.size(value)/self.sampleRate, 0, 1/self.sampleRate)
|
||||
self.RawXDataValid = True
|
||||
|
||||
self.rawdataY = value
|
||||
self.RawYDataValid = True
|
||||
self.plotAll()
|
||||
return
|
||||
|
||||
def callbackFuncBuffIdAct(self, value):
|
||||
if self.NMtn is None:
|
||||
return
|
||||
if(self.NMtn>0):
|
||||
self.progressBar.setValue(value/self.NMtn*100)
|
||||
if value/self.NMtn*100 < 80 and value/self.NMtn*100 >1:
|
||||
self.MtnYDataValid = False
|
||||
self.RawYDataValid = False
|
||||
return
|
||||
|
||||
###### Widget callbacks
|
||||
def pauseBtnAction(self):
|
||||
self.pause = not self.pause
|
||||
if self.pause:
|
||||
self.pauseBtn.setStyleSheet("background-color: red")
|
||||
else:
|
||||
self.pvPrefixStr = self.pvPrefixOrigStr # Restore if dataset was opened
|
||||
self.mtnPluginId = self.mtnPluginOrigId # Restore if dataset was opened
|
||||
self.buildPvNames()
|
||||
|
||||
self.pauseBtn.setStyleSheet("background-color: green")
|
||||
# Retrigger plots with newest values
|
||||
self.comSignalSpectY.data_signal.emit(self.spectY)
|
||||
self.comSignalRawData.data_signal.emit(self.rawdataY)
|
||||
return
|
||||
|
||||
def enableBtnAction(self):
|
||||
self.enable = not self.enable
|
||||
self.pvs['EnaCmd-RB'].put(self.enable)
|
||||
if self.enable:
|
||||
self.enableBtn.setStyleSheet("background-color: green")
|
||||
else:
|
||||
self.enableBtn.setStyleSheet("background-color: red")
|
||||
return
|
||||
|
||||
def triggBtnAction(self):
|
||||
self.pvTrigg.put(True)
|
||||
return
|
||||
|
||||
def zoomBtnAction(self):
|
||||
|
||||
if self.rawdataY is None:
|
||||
return
|
||||
if self.rawdataX is None:
|
||||
return
|
||||
if self.spectY is None:
|
||||
return
|
||||
if self.dataX is None:
|
||||
return
|
||||
if self.axSpect is None:
|
||||
return
|
||||
|
||||
# Spect
|
||||
self.axSpect.autoscale(enable=True)
|
||||
self.plotSpect(True)
|
||||
# rawdata
|
||||
self.plotRaw(True)
|
||||
|
||||
return
|
||||
|
||||
def newModeIndexChanged(self,index):
|
||||
if index==0 or index==1:
|
||||
if not self.offline and self.pvs['Mde-RB'] is not None:
|
||||
self.pvs['Mde-RB'].put(index+1)
|
||||
return
|
||||
|
||||
def openBtnAction(self):
|
||||
#if not self.offline:
|
||||
# self.pause = 1 # pause while open if online
|
||||
# self.pauseBtn.setStyleSheet("background-color: red")
|
||||
# QCoreApplication.processEvents()
|
||||
#
|
||||
#fname = QFileDialog.getOpenFileName(self, 'Open file', self.path, "Data files (*.npz)")
|
||||
#if fname is None:
|
||||
# return
|
||||
#if np.size(fname) != 2:
|
||||
# return
|
||||
#if len(fname[0])<=0:
|
||||
# return
|
||||
#self.path = os.path.dirname(os.path.abspath(fname[0]))
|
||||
#
|
||||
#npzfile = np.load(fname[0])
|
||||
#
|
||||
## verify scope plugin
|
||||
#if npzfile['plugin'] != "Mtn":
|
||||
# print ("Invalid data type (wrong plugin type)")
|
||||
# return
|
||||
#
|
||||
## File valid
|
||||
#self.rawdataX = npzfile['rawdataX']
|
||||
#self.rawdataY = npzfile['rawdataY']
|
||||
#self.dataX = npzfile['spectX']
|
||||
#self.spectY = npzfile['spectY']
|
||||
#self.sampleRate = npzfile['sampleRate']
|
||||
#self.NMtn = npzfile['NMtn']
|
||||
#self.mode = npzfile['mode']
|
||||
#self.pvPrefixStr = str(npzfile['pvPrefixStr'])
|
||||
#self.mtnPluginId = npzfile['mtnPluginId']
|
||||
#if 'unitRawY' in npzfile:
|
||||
# self.unitRawY = str(npzfile['unitRawY'])
|
||||
#if 'unitSpectY' in npzfile:
|
||||
# self.unitSpectY = str(npzfile['unitSpectY'])
|
||||
#if 'labelRawY' in npzfile:
|
||||
# self.labelRawY = str(npzfile['labelRawY'])
|
||||
#if 'labelSpectY' in npzfile:
|
||||
# self.labelSpectY = str(npzfile['labelSpectY'])
|
||||
#if 'title' in npzfile:
|
||||
# self.title = str(npzfile['title'])
|
||||
#
|
||||
#self.buildPvNames()
|
||||
#
|
||||
## trigg draw
|
||||
#self.MtnYDataValid = True
|
||||
#self.MtnXDataValid = True
|
||||
#self.RawYDataValid = True
|
||||
#self.RawXDataValid = True
|
||||
#self.sampleRateValid = True
|
||||
|
||||
|
||||
#self.comSignalMode.data_signal.emit(self.mode)
|
||||
#self.comSignalX.data_signal.emit(self.dataX)
|
||||
#self.comSignalSpectY.data_signal.emit(self.spectY)
|
||||
#self.comSignalRawData.data_signal.emit(self.rawdataY)
|
||||
|
||||
#self.setStatusOfWidgets()
|
||||
#
|
||||
#self.startupDone=True
|
||||
#self.zoomBtnAction()
|
||||
return
|
||||
|
||||
def saveBtnAction(self):
|
||||
#fname = QFileDialog.getSaveFileName(self, 'Save file', self.path, "Data files (*.npz)")
|
||||
#if fname is None:
|
||||
# return
|
||||
#if np.size(fname) != 2:
|
||||
# return
|
||||
#if len(fname[0])<=0:
|
||||
# return
|
||||
## Save all relevant data
|
||||
#np.savez(fname[0],
|
||||
# plugin = "Mtn",
|
||||
# rawdataX = self.rawdataX,
|
||||
# rawdataY = self.rawdataY,
|
||||
# spectX = self.dataX,
|
||||
# spectY = self.spectY,
|
||||
# sampleRate = self.sampleRate,
|
||||
# NMtn = self.NMtn,
|
||||
# mode = self.mode,
|
||||
# pvPrefixStr = self.pvPrefixStr,
|
||||
# mtnPluginId = self.mtnPluginId,
|
||||
# unitRawY = self.unitRawY,
|
||||
# unitSpectY = self.unitSpectY,
|
||||
# labelRawY = self.labelRawY,
|
||||
# labelSpectY = self.labelSpectY,
|
||||
# title = self.title
|
||||
# )
|
||||
#
|
||||
#self.path = os.path.dirname(os.path.abspath(fname[0]))
|
||||
|
||||
return
|
||||
|
||||
def plotAll(self):
|
||||
print("self.MtnYDataValid = " + str(self.MtnYDataValid))
|
||||
print("self.MtnXDataValid = " + str(self.MtnXDataValid))
|
||||
print("self.RawYDataValid = " + str(self.RawYDataValid))
|
||||
print("self.RawXDataValid = " + str(self.RawXDataValid))
|
||||
|
||||
if self.MtnYDataValid and self.MtnXDataValid and self.RawYDataValid and self.RawXDataValid:
|
||||
self.plotSpect()
|
||||
self.plotRaw()
|
||||
self.MtnYDataValid = False
|
||||
self.RawYDataValid = False
|
||||
|
||||
###### Plotting
|
||||
def plotSpect(self, autozoom=False):
|
||||
if self.dataX is None:
|
||||
return
|
||||
if self.spectY is None:
|
||||
return
|
||||
|
||||
# create an axis for spectrum
|
||||
if self.axSpect is None:
|
||||
self.axSpect = self.figure.add_subplot(212)
|
||||
|
||||
# plot data
|
||||
if self.plottedLineSpect is not None:
|
||||
self.plottedLineSpect.remove()
|
||||
|
||||
self.plottedLineSpect, = self.axSpect.plot(self.dataX,self.spectY, 'b*-')
|
||||
self.axSpect.grid(True)
|
||||
|
||||
|
||||
self.axSpect.set_xlabel("Frequency [Hz]")
|
||||
self.axSpect.set_ylabel(self.labelSpectY + ' ' +self.unitSpectY)
|
||||
|
||||
if autozoom:
|
||||
ymin = np.min(self.spectY)
|
||||
ymax = np.max(self.spectY)
|
||||
# ensure different values
|
||||
if ymin == ymax:
|
||||
ymin = ymin - 1
|
||||
ymax = ymax + 1
|
||||
range = ymax - ymin
|
||||
ymax += range * 0.1
|
||||
ymin -= range * 0.1
|
||||
xmin = np.min(self.dataX)
|
||||
xmax = np.max(self.dataX)
|
||||
if xmin == xmax:
|
||||
xmin = xmin - 1
|
||||
xmax = xmax + 1
|
||||
range = xmax - xmin
|
||||
xmax += range * 0.02
|
||||
xmin -= range * 0.02
|
||||
self.axSpect.set_ylim(ymin,ymax)
|
||||
self.axSpect.set_xlim(xmin,xmax)
|
||||
|
||||
# refresh canvas
|
||||
self.canvas.draw()
|
||||
self.axSpect.autoscale(enable=False)
|
||||
|
||||
def plotRaw(self, autozoom=False):
|
||||
if self.rawdataY is None:
|
||||
return
|
||||
|
||||
if self.rawdataX is None:
|
||||
return
|
||||
|
||||
# create an axis for spectrum
|
||||
if self.axRaw is None:
|
||||
self.axRaw = self.figure.add_subplot(211)
|
||||
|
||||
|
||||
# plot data
|
||||
if self.plottedLineRaw is not None:
|
||||
self.plottedLineRaw.remove()
|
||||
|
||||
self.plottedLineRaw, = self.axRaw.plot(self.rawdataX,self.rawdataY, 'b*-')
|
||||
self.axRaw.grid(True)
|
||||
|
||||
self.axRaw.set_xlabel('Time [s]')
|
||||
self.axRaw.set_ylabel(self.labelRawY + ' ' + self.unitRawY)
|
||||
self.axRaw.set_title(self.title)
|
||||
|
||||
if autozoom:
|
||||
ymin = np.min(self.rawdataY)
|
||||
ymax = np.max(self.rawdataY)
|
||||
# ensure different values
|
||||
if ymin == ymax:
|
||||
ymin=ymin-1
|
||||
ymax=ymax+1
|
||||
range = ymax - ymin
|
||||
ymax += range * 0.1
|
||||
ymin -= range * 0.1
|
||||
xmin = np.min(self.rawdataX)
|
||||
xmax = np.max(self.rawdataX)
|
||||
if xmin == xmax:
|
||||
xmin = xmin - 1
|
||||
xmax = xmax + 1
|
||||
range = xmax - xmin
|
||||
xmax += range * 0.02
|
||||
xmin -= range * 0.02
|
||||
self.axRaw.set_ylim(ymin,ymax)
|
||||
self.axRaw.set_xlim(xmin,xmax)
|
||||
|
||||
# refresh canvas
|
||||
self.canvas.draw()
|
||||
self.allowSave = True
|
||||
self.saveBtn.setEnabled(True)
|
||||
self.axRaw.autoscale(enable=False)
|
||||
|
||||
def printOutHelp():
|
||||
print("ecmcMtnMainGui: Plots waveforms of Mtn data (updates on Y data callback). ")
|
||||
print("python ecmcMtnMainGui.py <prefix> <mtnId>")
|
||||
print("<prefix>: Ioc prefix ('IOC_TEST:')")
|
||||
print("<mtnId> : Id of mtn plugin ('0')")
|
||||
print("example : python ecmcMotionMainGui.py 'IOC_TEST:' '0'")
|
||||
print("Will connect to Pvs: <prefix>Plg-Mtn<mtnId>-*")
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
prefix = None
|
||||
mtnid = None
|
||||
if len(sys.argv) == 1:
|
||||
prefix = None
|
||||
mtnid = None
|
||||
elif len(sys.argv) == 3:
|
||||
prefix = sys.argv[1]
|
||||
mtnid = int(sys.argv[2])
|
||||
else:
|
||||
printOutHelp()
|
||||
sys.exit()
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
window=ecmcMtnMainGui(prefix=prefix,mtnPluginId=mtnid)
|
||||
window.show()
|
||||
sys.exit(app.exec_())
|
||||
Reference in New Issue
Block a user