Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Bliss
bliss
Commits
04939dc4
Commit
04939dc4
authored
May 21, 2021
by
Matias Guijarro
Browse files
Merge branch 'pi_e753_wave' into 'master'
pi-e753: manage low level wave. See merge request
!3265
parents
b1b71941
1ab2e27b
Pipeline
#47126
failed with stages
in 105 minutes and 30 seconds
Changes
9
Pipelines
1
Expand all
Hide whitespace changes
Inline
Side-by-side
CHANGELOG.md
View file @
04939dc4
...
...
@@ -57,6 +57,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
-
Improved management of current_configuration / default_configuration
-
Project: Remove
`-conda`
suffix from requirement files
-
PI E712 E753
-
uniformization of communication and recorder.
-
"wave" motion generator.
### Fixed
-
Flint
...
...
bliss/controllers/motors/pi_e51x.py
View file @
04939dc4
...
...
@@ -28,6 +28,21 @@ from bliss.common import event
from
.
import
pi_gcs
"""
Special commands, e.g. fast polling commands, consist only of one
character. The 24th ASCII character e.g. is called #24. Note that
these commands are not followed by a termination character (but the
responses to them are).
* #5: Request Motion Status
* #6: Query If Position Has Changed Since Last POS? Command
* #7: Request Controller Ready Status
* #8: Query If Macro Is Running
* #9: Get Wave Generator Status
* #24: Stop All Motion
"""
class
PI_E51X
(
Controller
):
""" Base class for E517 and E518
"""
...
...
bliss/controllers/motors/pi_e712.py
View file @
04939dc4
...
...
@@ -6,24 +6,19 @@
# Distributed under the GNU LGPLv3. See LICENSE for more info.
import
time
import
re
import
numpy
import
weakref
import
gevent
from
bliss.controllers.motor
import
Controller
from
bliss.common.utils
import
object_method
from
bliss.common.utils
import
grouped
from
bliss.common.utils
import
add_property
from
bliss
import
global_map
from
bliss.common.axis
import
AxisState
,
Motion
,
CyclicTrajectory
from
bliss.config.channels
import
Cache
from
bliss.common.switch
import
Switch
as
BaseSwitch
from
bliss.common.logtools
import
*
from
bliss.common.logtools
import
log_info
,
log_debug
,
log_error
from
.
import
pi_gcs
from
bliss.comm.util
import
TCP
from
bliss.common.event
import
connect
,
disconnect
"""
Bliss controller for ethernet PI E712 piezo controller.
...
...
@@ -55,35 +50,17 @@ config example:
"""
class
PI_E712
(
Controller
):
# POSSIBLE DATA TRIGGER SOURCE
WAVEFORM
=
0
MOTION
=
1
EXTERNAL
=
3
IMMEDIATELY
=
4
class
PI_E712
(
pi_gcs
.
Communication
,
pi_gcs
.
Recorder
,
Controller
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
pi_gcs
.
Communication
.
__init__
(
self
)
pi_gcs
.
Recorder
.
__init__
(
self
)
Controller
.
__init__
(
self
,
*
args
,
**
kwargs
)
self
.
sock
=
None
self
.
cname
=
"E712"
self
.
__axis_closed_loop
=
weakref
.
WeakKeyDictionary
()
def
initialize
(
self
):
"""
Controller intialization : opens a single socket for all 3 axes.
"""
self
.
sock
=
pi_gcs
.
get_pi_comm
(
self
.
config
,
TCP
)
global_map
.
register
(
self
,
children_list
=
[
self
.
sock
])
connect
(
self
.
sock
,
"connect"
,
self
.
_clear_error
)
def
finalize
(
self
):
"""
Closes the controller socket.
"""
if
self
.
sock
:
self
.
sock
.
close
()
disconnect
(
self
.
sock
,
"connect"
,
self
.
_clear_error
)
self
.
com_initialize
()
def
initialize_axis
(
self
,
axis
):
"""
...
...
@@ -121,26 +98,7 @@ class PI_E712(Controller):
log_debug
(
self
,
"axis = %r"
%
axis
.
name
)
# POSSIBLE DATA RECORDER TYPE
axis
.
TARGET_POSITION_OF_AXIS
=
1
axis
.
CURRENT_POSITION_OF_AXIS
=
2
axis
.
POSITION_ERROR_OF_AXIS
=
3
axis
.
CONTROL_VOLTAGE_OF_OUTPUT_CHAN
=
7
axis
.
DDL_OUTPUT_OF_AXIS
=
13
axis
.
OPEN_LOOP_CONTROL_OF_AXIS
=
14
axis
.
CONTROL_OUTPUT_OF_AXIS
=
15
axis
.
VOLTAGE_OF_OUTPUT_CHAN
=
16
axis
.
SENSOR_NORMALIZED_OF_INPUT_CHAN
=
17
axis
.
SENSOR_FILTERED_OF_INPUT_CHAN
=
18
axis
.
SENSOR_ELECLINEAR_OF_INPUT_CHAN
=
19
axis
.
SENSOR_MECHLINEAR_OF_INPUT_CHAN
=
20
axis
.
SLOWED_TARGET_OF_AXIS
=
22
# POSSIBLE DATA TRIGGER SOURCE
axis
.
WAVEFORM
=
0
axis
.
MOTION
=
1
axis
.
EXTERNAL
=
3
axis
.
IMMEDIATELY
=
4
self
.
_add_recoder_enum_on_axis
(
axis
)
# supposed that we are on target on init
axis
.
_last_on_target
=
True
...
...
@@ -313,14 +271,6 @@ class PI_E712(Controller):
""" RAW COMMANDS """
def
raw_write
(
self
,
axis
,
com
):
com
=
com
.
encode
()
self
.
sock
.
write
(
b
"%s
\n
"
%
com
)
def
raw_write_read
(
self
,
axis
,
com
):
com
=
com
.
encode
()
return
self
.
sock
.
write_readline
(
b
"%s
\n
"
%
com
)
def
get_identifier
(
self
,
axis
):
"""
Returns Identification information (`*IDN?` command).
...
...
@@ -338,259 +288,6 @@ class PI_E712(Controller):
)
return
info_str
def
command
(
self
,
cmd
,
nb_line
=
1
):
"""
Method to send a command to the controller.
Read answer if needed (ie. `cmd` contains a `?`).
- Encode `cmd` string.
- Add `
\\
n` terminator.
"""
with
self
.
sock
.
lock
:
cmd
=
cmd
.
strip
()
need_reply
=
cmd
.
find
(
"?"
)
>
-
1
cmd
=
cmd
.
encode
()
if
need_reply
:
if
nb_line
>
1
:
reply
=
self
.
sock
.
write_readlines
(
cmd
+
b
"
\n
"
,
nb_line
)
else
:
reply
=
self
.
sock
.
write_readline
(
cmd
+
b
"
\n
"
)
if
not
reply
:
# it's an error
errors
=
[
self
.
name
]
+
list
(
self
.
get_error
())
raise
RuntimeError
(
"Device {0} error nb {1} => ({2})"
.
format
(
*
errors
)
)
if
nb_line
>
1
:
parsed_reply
=
list
()
commands
=
cmd
.
split
(
b
"
\n
"
)
if
len
(
commands
)
==
nb_line
:
# one reply per command
for
cmd
,
rep
in
zip
(
commands
,
reply
):
space_pos
=
cmd
.
find
(
b
" "
)
if
space_pos
>
-
1
:
args
=
cmd
[
space_pos
+
1
:]
parsed_reply
.
append
(
self
.
_parse_reply
(
rep
,
args
))
else
:
parsed_reply
.
append
(
rep
)
else
:
# a command with several replies
space_pos
=
cmd
.
find
(
b
" "
)
if
space_pos
>
-
1
:
args
=
cmd
[
space_pos
+
1
:]
for
arg
,
rep
in
zip
(
args
.
split
(),
reply
):
parsed_reply
.
append
(
self
.
_parse_reply
(
rep
,
arg
))
reply
=
parsed_reply
else
:
space_pos
=
cmd
.
find
(
b
" "
)
if
space_pos
>
-
1
:
reply
=
self
.
_parse_reply
(
reply
,
cmd
[
space_pos
+
1
:])
else
:
reply
=
reply
.
decode
()
return
reply
else
:
self
.
sock
.
write
(
cmd
+
b
"
\n
"
)
errno
,
error_message
=
self
.
get_error
()
if
errno
:
errors
=
[
self
.
name
,
cmd
]
+
[
errno
,
error_message
]
raise
RuntimeError
(
"Device {0} command {1} error nb {2} => ({3})"
.
format
(
*
errors
)
)
def
get_data_len
(
self
):
"""
return how many point you can get from recorder
"""
return
int
(
self
.
command
(
"DRL? 1"
))
def
get_data_max_len
(
self
):
"""
return the maximum number of records
"""
return
int
(
self
.
command
(
"SPA? 1 0x16000200"
))
def
get_data
(
self
,
from_event_id
=
0
,
npoints
=
None
,
rec_table_id
=
None
):
"""
retrieved store data as a numpy structured array,
struct name will be the data_type + motor name.
i.e:
Target_Position_of_<motor_name> or Current_Position_of_<motor_name>
Args:
- from_event_id from which point id you want to read
- rec_table_id list of table you want to read, None means all
"""
if
rec_table_id
is
None
:
# All table
# just ask the first table because they have the same synchronization
nb_availabe_points
=
int
(
self
.
command
(
"DRL? 1"
))
nb_availabe_points
-=
from_event_id
if
npoints
is
None
:
npoints
=
nb_availabe_points
else
:
npoints
=
min
(
nb_availabe_points
,
npoints
)
cmd
=
b
"DRR? %d %d
\n
"
%
((
from_event_id
+
1
),
npoints
)
else
:
rec_tables
=
" "
.
join
((
str
(
x
)
for
x
in
rec_table_id
))
nb_points
=
self
.
command
(
"DRL? %s"
%
rec_tables
,
len
(
rec_table_id
))
if
isinstance
(
nb_points
,
list
):
nb_points
=
min
([
int
(
x
)
for
x
in
nb_points
])
else
:
nb_points
=
int
(
nb_points
)
point_2_read
=
nb_points
-
from_event_id
if
point_2_read
<
0
:
point_2_read
=
0
elif
npoints
is
not
None
and
point_2_read
>
npoints
:
point_2_read
=
npoints
cmd
=
b
"DRR? %d %d %s
\n
"
%
(
from_event_id
+
1
,
point_2_read
,
rec_tables
)
try
:
exception_occurred
=
False
with
self
.
sock
.
lock
:
self
.
sock
.
_write
(
cmd
)
# HEADER
header
=
dict
()
while
1
:
line
=
self
.
sock
.
readline
()
if
not
line
:
return
# no data available
if
line
.
find
(
b
"END_HEADER"
)
>
-
1
:
break
key
,
value
=
(
x
.
strip
().
decode
()
for
x
in
line
[
1
:].
split
(
b
"="
))
header
[
key
]
=
value
ndata
=
int
(
header
[
"NDATA"
])
separator
=
chr
(
int
(
header
[
"SEPARATOR"
])).
encode
()
sample_time
=
float
(
header
[
"SAMPLE_TIME"
])
dim
=
int
(
header
[
"DIM"
])
column_info
=
dict
()
keep_axes
=
{
x
.
channel
:
x
for
x
in
self
.
axes
.
values
()
if
hasattr
(
x
,
"channel"
)
}
for
name_id
in
range
(
8
):
try
:
desc
=
header
[
"NAME%d"
%
name_id
]
except
KeyError
:
break
else
:
axis_pos
=
desc
.
find
(
"axis"
)
if
axis_pos
<
0
:
axis_pos
=
desc
.
find
(
"chan"
)
axis_id
=
int
(
desc
[
axis_pos
+
len
(
"axis"
)
:])
if
axis_id
in
keep_axes
:
new_desc
=
desc
[:
axis_pos
]
+
keep_axes
[
axis_id
].
name
column_info
[
name_id
]
=
new_desc
.
replace
(
" "
,
"_"
)
dtype
=
[(
"timestamp"
,
"f8"
)]
dtype
+=
[(
name
,
"f8"
)
for
name
in
column_info
.
values
()]
data
=
numpy
.
zeros
(
ndata
,
dtype
=
dtype
)
data
[
"timestamp"
]
=
(
numpy
.
arange
(
from_event_id
,
from_event_id
+
ndata
)
*
sample_time
)
for
line_id
in
range
(
ndata
):
line
=
self
.
sock
.
readline
().
strip
()
values
=
line
.
split
(
separator
)
for
column_id
,
name
in
column_info
.
items
():
data
[
name
][
line_id
]
=
values
[
column_id
]
return
data
except
:
exception_occurred
=
True
try
:
errno
,
error_message
=
self
.
get_error
()
except
:
pass
self
.
sock
.
close
()
# safe in case of ctrl-c
raise
finally
:
if
not
exception_occurred
:
errno
,
error_message
=
self
.
get_error
()
# If we ask data in advance, ** Out of range **
# error is return.
# in that case it's not an error
if
errno
>
0
and
errno
!=
17
:
errors
=
[
self
.
name
,
"get_data"
]
+
[
errno
,
error_message
]
raise
RuntimeError
(
"Device {0} command {1} error nb {2} => ({3})"
.
format
(
*
errors
)
)
def
set_recorder_data_type
(
self
,
*
motor_data_type
):
"""
Configure the data recorder
Args:
motor_data_type should be a list of tuple with motor and datatype
i.e: motor_data_type=[px,px.CURRENT_POSITION_OF_AXIS,
py,py.CURRENT_POSITION_OF_AXIS]
"""
nb_recorder_table
=
len
(
motor_data_type
)
/
2
if
nb_recorder_table
*
2
!=
len
(
motor_data_type
):
raise
RuntimeError
(
"Argument must be grouped by 2 "
"(motor1,data_type1,motor2,data_type2...)"
)
self
.
command
(
"SPA 1 0x16000300 %d"
%
nb_recorder_table
)
max_nb_recorder
=
int
(
self
.
command
(
"TNR?"
))
if
nb_recorder_table
>
max_nb_recorder
:
raise
RuntimeError
(
"Device %s too many recorder data, can only record %d"
%
(
self
.
name
,
max_nb_recorder
)
)
cmd
=
"DRC "
cmd
+=
" "
.
join
(
(
"%d %s %d"
%
(
rec_id
+
1
,
motor
.
channel
,
data_type
)
for
rec_id
,
(
motor
,
data_type
)
in
enumerate
(
grouped
(
motor_data_type
,
2
))
)
)
self
.
command
(
cmd
)
def
start_recording
(
self
,
trigger_source
,
value
=
0
,
recorder_rate
=
None
):
"""
start recording data according to what was asked to record.
@see set_recorder_data_type
Args:
- trigger_source could be WAVEFORM,MOTION,EXTERNAL,IMMEDIATELY
- value for EXTERNAL value is the trigger input line (0 mean all)
- recorder_rate if None max speed otherwise the period in seconds
"""
if
trigger_source
not
in
(
self
.
WAVEFORM
,
self
.
MOTION
,
self
.
EXTERNAL
,
self
.
IMMEDIATELY
,
):
raise
RuntimeError
(
"Device %s trigger source can only be:"
"WAVEFORM,MOTION,EXTERNAL or IMMEDIATELY"
)
if
recorder_rate
is
not
None
:
cycle_time
=
float
(
self
.
command
(
"SPA? 1 0xe000200"
))
rate
=
int
(
recorder_rate
/
cycle_time
)
# should be faster than asked
else
:
rate
=
1
self
.
command
(
"RTR %d"
%
rate
)
nb_recorder
=
int
(
self
.
command
(
"TNR?"
))
cmd
=
"DRT "
cmd
+=
" "
.
join
(
(
"%d %d %d"
%
(
rec_id
,
trigger_source
,
value
)
for
rec_id
in
range
(
1
,
nb_recorder
+
1
)
)
)
self
.
command
(
cmd
)
def
get_recorder_data_rate
(
self
):
"""
return the rate of the data recording in seconds
"""
cycle_time
,
rtr
=
self
.
command
(
"SPA? 1 0xe000200
\n
RTR?"
,
2
)
return
float
(
cycle_time
)
*
int
(
rtr
)
def
output_position_gate
(
self
,
axis
,
position_1
,
position_2
,
output
=
1
):
"""
This program an external gate on the specified output.
...
...
@@ -756,14 +453,6 @@ class PI_E712(Controller):
axes_str
=
" "
.
join
([
"%d 0"
%
t
.
axis
.
channel
for
t
in
trajectories
])
self
.
command
(
"WGO "
+
axes_str
)
def
_parse_reply
(
self
,
reply
,
args
):
args_pos
=
reply
.
find
(
b
"="
)
if
reply
[:
args_pos
]
!=
args
:
# weird
print
(
"Weird thing happens with connection of %s"
%
self
.
name
)
return
reply
.
decode
()
else
:
return
reply
[
args_pos
+
1
:].
decode
()
def
_get_pos
(
self
,
channel
):
"""
Args:
...
...
@@ -864,16 +553,6 @@ class PI_E712(Controller):
axis
.
_last_on_target
=
last_on_target
return
last_on_target
def
get_error
(
self
):
_error_number
=
int
(
self
.
sock
.
write_readline
(
b
"ERR?
\n
"
))
_error_str
=
pi_gcs
.
get_error_str
(
_error_number
)
return
(
_error_number
,
_error_str
)
def
_clear_error
(
self
,
connected
):
if
connected
:
self
.
get_error
()
# read and clear any error
@
object_method
(
types_info
=
(
"None"
,
"string"
))
def
get_info
(
self
,
axis
):
""" Return hw info
...
...
bliss/controllers/motors/pi_e753.py
View file @
04939dc4
This diff is collapsed.
Click to expand it.
bliss/controllers/motors/pi_gcs.py
View file @
04939dc4
...
...
@@ -6,11 +6,18 @@
# Distributed under the GNU LGPLv3. See LICENSE for more info.
# Distributed under the GNU LGPLv3. See LICENSE.txt for more info.
# PI GCS
"""
This module is a common base for PI controllers:
* for communication
* fro Wave generator
"""
from
warnings
import
warn
import
numpy
from
bliss.comm.util
import
get_comm
,
get_comm_type
,
TCP
from
bliss.common.event
import
connect
,
disconnect
from
bliss.common.utils
import
grouped
from
bliss
import
global_map
def
get_pi_comm
(
config
,
ctype
=
None
,
**
opts
):
...
...
@@ -415,3 +422,390 @@ pi_gcs_errors = {
-
1080
:
"Parameter DAT file does not have correct revision."
,
-
1081
:
"User stages DAT file does not have correct revision."
,
}
class
Communication
:
def
__init__
(
self
):
self
.
sock
=
None
def
com_initialize
(
self
):
self
.
sock
=
get_pi_comm
(
self
.
config
,
TCP
)
global_map
.
register
(
self
,
children_list
=
[
self
.
sock
])
# ???
connect
(
self
.
sock
,
"connect"
,
self
.
_clear_error
)
def
finalize
(
self
):
"""
Closes the controller socket.
"""
self
.
close
()
disconnect
(
self
.
sock
,
"connect"
,
self
.
_clear_error
)
def
close
(
self
):
if
self
.
sock
:
self
.
sock
.
close
()
def
get_error
(
self
):
_error_number
=
int
(
self
.
sock
.
write_readline
(
b
"ERR?
\n
"
))
_error_str
=
get_error_str
(
_error_number
)
return
(
_error_number
,
_error_str
)
def
_clear_error
(
self
,
connected
):
if
connected
:
self
.
get_error
()
# read and clear any error
def
command
(
self
,
cmd
,
nb_line
=
1
):
"""
Method to send a command to the controller.
Read answer if needed (ie. `cmd` contains a `?`).
Parameters:
<cmd>: str
Command. Not encoded; Without terminator character.
[<nb_line>]: int
Number of lines expected in answer.
For multi-lines commands (ex: IFC?) or multiple commands.
Returns: str ; list of str ; tuple of str
Usage:
* id = self.command("*IDN?")
* ont = self.command("ONT? 1")
* ans = self.command("SPA? 1 0x07000A00")
* com_pars_list = self.command("IFC?", 5)
* pos, vel = self.command("POS? 1
\n
VEL? 1", 2)
Note:
Does not work for single char commands (#5 #9 #24 etc.)
"""
with
self
.
sock
.
lock
:
cmd
=
cmd
.
strip
()
need_reply
=
cmd
.
find
(
"?"
)
>
-
1
cmd
=
cmd
.
encode
()
if
need_reply
:
if
nb_line
>
1
:
reply
=
self
.
sock
.
write_readlines
(
cmd
+
b
"
\n
"
,
nb_line
)
else
:
reply
=
self
.
sock
.
write_readline
(
cmd
+
b
"
\n
"
)
if
not
reply
:
# it's an error
errors
=
[
self
.
name
]
+
list
(
self
.
get_error
())
raise
RuntimeError
(
"PI Device {0} error nb {1} => ({2})"
.
format
(
*
errors
)
)
if
nb_line
>
1
:
# Multi-lines answer or multiple commands
parsed_reply
=
list
()
commands
=
cmd
.
split
(
b
"
\n
"
)
if
len
(
commands
)
==
nb_line
:
# Many queries, one reply per query
# Return a tuple of str
for
cmd
,
rep
in
zip
(
commands
,
reply
):
space_pos
=
cmd
.
find
(
b
" "
)
if
space_pos
>
-
1
:
args
=
cmd
[
space_pos
+
1
:]
parsed_reply
.
append
(
self
.
_parse_reply
(
rep
,
args
,
cmd
))
else
:
# No space in cmd => no param to parse. ex: "*IDN?" "CCL?"
parsed_reply
.
append
(
rep
)
else
:
# One command with reply in several lines
# Return a list of str
space_pos
=
cmd
.
find
(
b
" "
)
if
space_pos
>
-
1
:
args
=
cmd
[
space_pos
+
1
:]
for
arg
,
rep
in
zip
(
args
.
split
(),
reply
):
parsed_reply
.
append
(
self
.
_parse_reply
(
rep
,
arg
,
cmd
))
else
:
# TSP? TAD? IFC? etc.
for
ans
in
reply
:
parsed_reply
.
append
(
ans
.
decode
())
reply
=
parsed_reply
else
:
# Single line answer.
# Example: cmd = "VEL? 1"
space_pos
=
cmd
.
find
(
b
" "
)
# print(f"cmd={cmd} space_pos={space_pos} reply={reply} ")
if
space_pos
>
-
1
:
axes_arg
=
cmd
[
space_pos
+
1
:
]
# 2nd part of the command -> axes id.
reply
=
self
.
_parse_reply
(
reply
,
axes_arg
,
cmd
)
else
:
reply
=
reply
.
decode
()
return
reply
else
:
# no reply expected.
self
.
sock
.
write
(
cmd
+
b
"
\n
"
)
errno
,
error_message
=
self
.
get_error
()
if
errno
:
errors
=
[
self
.
name
,
cmd
]
+
[
errno
,
error_message
]
raise
RuntimeError
(
"Device {0} command {1} error nb {2} => ({3})"
.
format
(
*
errors
)
)
def
raw_write
(
self
,
axis
,
com
):
com
=
com
.
encode
()
self
.
sock
.
write
(
b
"%s
\n
"
%
com
)
def
raw_write_read
(
self
,
axis
,
com
):
com
=
com
.
encode
()
return
self
.
sock
.
write_readline
(
b
"%s
\n
"
%
com
)
def
_parse_reply
(
self
,
reply
,
args
,
cmd
):
"""
Extract pertinent value in controller's answer.
<reply>: answer of the controller.