newportxps 0.2__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- newportxps-0.3.0/LICENSE +25 -0
- newportxps-0.3.0/PKG-INFO +121 -0
- {newportxps-0.2 → newportxps-0.3.0}/newportxps/XPS_C8_drivers.py +38 -8
- {newportxps-0.2 → newportxps-0.3.0}/newportxps/ftp_wrapper.py +21 -19
- {newportxps-0.2 → newportxps-0.3.0}/newportxps/newportxps.py +163 -53
- newportxps-0.3.0/newportxps/utils.py +33 -0
- newportxps-0.3.0/newportxps.egg-info/PKG-INFO +121 -0
- {newportxps-0.2 → newportxps-0.3.0}/newportxps.egg-info/SOURCES.txt +2 -0
- newportxps-0.3.0/pyproject.toml +45 -0
- newportxps-0.3.0/setup.py +6 -0
- newportxps-0.2/PKG-INFO +0 -10
- newportxps-0.2/newportxps/utils.py +0 -22
- newportxps-0.2/newportxps.egg-info/PKG-INFO +0 -10
- newportxps-0.2/setup.py +0 -13
- {newportxps-0.2 → newportxps-0.3.0}/README.md +0 -0
- {newportxps-0.2 → newportxps-0.3.0}/newportxps/__init__.py +0 -0
- {newportxps-0.2 → newportxps-0.3.0}/newportxps/debugtime.py +0 -0
- {newportxps-0.2 → newportxps-0.3.0}/newportxps.egg-info/dependency_links.txt +0 -0
- {newportxps-0.2 → newportxps-0.3.0}/newportxps.egg-info/requires.txt +0 -0
- {newportxps-0.2 → newportxps-0.3.0}/newportxps.egg-info/top_level.txt +0 -0
- {newportxps-0.2 → newportxps-0.3.0}/setup.cfg +0 -0
newportxps-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018, Matthew Newville, The University of Chicago
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
20
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
21
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
22
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
23
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
24
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
25
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: newportxps
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Python interface to Newport XPS motion controllers
|
|
5
|
+
Author-email: Matthew Newville <newville@cars.uchicago.edu>
|
|
6
|
+
License: BSD License
|
|
7
|
+
Keywords: motion control,data collection
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Education
|
|
11
|
+
Classifier: Intended Audience :: Other Audience
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: Implementation :: Jython
|
|
23
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
24
|
+
Classifier: Topic :: Education
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
27
|
+
Classifier: Topic :: Software Development
|
|
28
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
29
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
|
+
Classifier: Topic :: Utilities
|
|
31
|
+
Requires-Python: >=3.8
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
Requires-Dist: pysftp
|
|
35
|
+
|
|
36
|
+
# newportxps
|
|
37
|
+
|
|
38
|
+
This module provides code for using Newport XPS motor controllers from Python.
|
|
39
|
+
|
|
40
|
+
While Newport Corp. has provided a basic socket and ftp interface to the XPS
|
|
41
|
+
controller for a long time, this interface is very low-level. In addition,
|
|
42
|
+
there are some incompatibilities between the different generations of XPS
|
|
43
|
+
controllers (generations C, Q, D in that chronological order), and a lack of
|
|
44
|
+
support for Python 3 in the Newport-provided interface. The `newportxps`
|
|
45
|
+
module here aims to provide a simple, user-friendly interface for the Newport
|
|
46
|
+
XPS that works uniformly for all three generations of XPS and for both Python
|
|
47
|
+
2 and 3.
|
|
48
|
+
|
|
49
|
+
As an example, connecting to and reading the status of an XPS controller may
|
|
50
|
+
look like this:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
>>> from newportxps import NewportXPS
|
|
54
|
+
>>> xps = NewportXPS('164.54.160.000', username='Administrator', password='Please.Let.Me.In')
|
|
55
|
+
>>> print(xps.status_report())
|
|
56
|
+
# XPS host: 164.54.160.000 (164.54.160.000)
|
|
57
|
+
# Firmware: XPS-D-N13006
|
|
58
|
+
# Current Time: Sun Sep 16 13:40:24 2018
|
|
59
|
+
# Last Reboot: Wed Sep 12 14:46:44 2018
|
|
60
|
+
# Trajectory Group: None
|
|
61
|
+
# Groups and Stages
|
|
62
|
+
DetectorZ (SingleAxisInUse), Status: Ready state from motion
|
|
63
|
+
DetectorZ.Pos (ILS@ILS150CC@XPS-DRV11)
|
|
64
|
+
Hardware Status: First driver powered on - ZM low level
|
|
65
|
+
Positioner Errors: OK
|
|
66
|
+
SampleX (SingleAxisInUse), Status: Ready state from motion
|
|
67
|
+
SampleX.Pos (UTS@UTS150PP@XPS-DRV11)
|
|
68
|
+
Hardware Status: First driver powered on - ZM high level
|
|
69
|
+
Positioner Errors: OK
|
|
70
|
+
SampleY (SingleAxisInUse), Status: Ready state from motion
|
|
71
|
+
SampleY.Pos (UTS@UTS150PP@XPS-DRV11)
|
|
72
|
+
Hardware Status: First driver powered on - ZM high level
|
|
73
|
+
Positioner Errors: OK
|
|
74
|
+
SampleZ (SingleAxisInUse), Status: Ready state from motion
|
|
75
|
+
SampleZ.Pos (UTS@UTS150PP@XPS-DRV11)
|
|
76
|
+
Hardware Status: First driver powered on - ZM low level
|
|
77
|
+
Positioner Errors: OK
|
|
78
|
+
|
|
79
|
+
>>> for gname, info in xps.groups.items():
|
|
80
|
+
... print(gname, info)
|
|
81
|
+
...
|
|
82
|
+
DetectorX {'category': 'SingleAxisInUse', 'positioners': ['Pos']}
|
|
83
|
+
SampleX {'category': 'SingleAxisInUse', 'positioners': ['Pos']}
|
|
84
|
+
SampleY {'category': 'SingleAxisInUse', 'positioners': ['Pos']}
|
|
85
|
+
SampleZ {'category': 'SingleAxisInUse', 'positioners': ['Pos']}
|
|
86
|
+
>>>
|
|
87
|
+
>>> for sname, info in xps.stages.items():
|
|
88
|
+
... print(sname, xps.get_stage_position(sname), info)
|
|
89
|
+
...
|
|
90
|
+
DetectorX.Pos 36.5 {'type': 'ILS@ILS150CC@XPS-DRV11', 'max_velo': 100, 'max_accel': 400, 'low_limit': -74, 'high_limit': 74}
|
|
91
|
+
SampleX.Pos 1.05 {'type': 'UTS@UTS150PP@XPS-DRV11', 'max_velo': 20, 'max_accel': 80, 'low_limit': -74, 'high_limit': 74}
|
|
92
|
+
SampleY.Pos 0.24 {'type': 'UTS@UTS150PP@XPS-DRV11', 'max_velo': 20, 'max_accel': 80, 'low_limit': -74, 'high_limit': 74}
|
|
93
|
+
SampleZ.Pos 2.5 {'type': 'UTS@UTS150PP@XPS-DRV11', 'max_velo': 20, 'max_accel': 80, 'low_limit': -74, 'high_limit': 74}
|
|
94
|
+
|
|
95
|
+
>>> xps.move_stage('SampleZ.Pos', 1.0)
|
|
96
|
+
|
|
97
|
+
>>> xps.home_group('DetectorX')
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
On creation and initialization of the NewportXPS, the Groups and status of the
|
|
103
|
+
controller are read in and Stages defined so that they can be queried or
|
|
104
|
+
moved.
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
The `NewportXPS` class has a number of methods to interact with the controller including:
|
|
108
|
+
|
|
109
|
+
* reboot controller
|
|
110
|
+
* get status, hardware errors, etc.
|
|
111
|
+
* save and upload new `system.ini` and `stages.ini` files.
|
|
112
|
+
* enable and disable Groups.
|
|
113
|
+
* initialize and home Stages and Groups of Stages.
|
|
114
|
+
* read Stage positions.
|
|
115
|
+
* move Stages and Groups to new positions.
|
|
116
|
+
* set Stage velocity.
|
|
117
|
+
* define simple linear trajectories (using PVT mode), both 'forward' and 'backward'.
|
|
118
|
+
* upload any PVT trajectory.
|
|
119
|
+
* arm PVT trajectory.
|
|
120
|
+
* run PVT trajectory.
|
|
121
|
+
* read and save Gathering file for a trajectory.
|
|
@@ -14,11 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
import sys
|
|
16
16
|
import socket
|
|
17
|
-
import six
|
|
18
|
-
from collections import OrderedDict
|
|
19
|
-
|
|
20
|
-
from .utils import bytes2str
|
|
21
17
|
|
|
18
|
+
from .utils import bytes2str, str2bytes
|
|
22
19
|
|
|
23
20
|
class XPSException(Exception):
|
|
24
21
|
"""XPS Controller Exception"""
|
|
@@ -41,7 +38,7 @@ class XPS:
|
|
|
41
38
|
XPS.__nbSockets = 0
|
|
42
39
|
for socketId in range(self.MAX_NB_SOCKETS):
|
|
43
40
|
XPS.__usedSockets[socketId] = 0
|
|
44
|
-
self.errorcodes =
|
|
41
|
+
self.errorcodes = {}
|
|
45
42
|
|
|
46
43
|
def withValidSocket(fcn):
|
|
47
44
|
""" decorator to ensure that a valid socket is passed as the
|
|
@@ -64,7 +61,7 @@ class XPS:
|
|
|
64
61
|
def __sendAndReceive (self, socketId, command):
|
|
65
62
|
# print("SEND REC ", command, type(command))
|
|
66
63
|
try:
|
|
67
|
-
XPS.__sockets[socketId].send(
|
|
64
|
+
XPS.__sockets[socketId].send(str2bytes(command))
|
|
68
65
|
ret = bytes2str(XPS.__sockets[socketId].recv(1024))
|
|
69
66
|
while (ret.find(',EndOfAPI') == -1):
|
|
70
67
|
ret += bytes2str(XPS.__sockets[socketId].recv(1024))
|
|
@@ -114,7 +111,7 @@ class XPS:
|
|
|
114
111
|
return -1
|
|
115
112
|
|
|
116
113
|
err, ret = self.ErrorListGet(socketId)
|
|
117
|
-
self.errorcodes =
|
|
114
|
+
self.errorcodes = {}
|
|
118
115
|
for cline in ret.split(';'):
|
|
119
116
|
if ':' in cline:
|
|
120
117
|
ecode, message = cline.split(':', 1)
|
|
@@ -233,7 +230,7 @@ class XPS:
|
|
|
233
230
|
|
|
234
231
|
# TimerSet : Set a timer
|
|
235
232
|
def TimerSet (self, socketId, TimerName, FrequencyTicks):
|
|
236
|
-
return self.Send(socketId, 'TimerSet(%s, %s)' (TimerName, str(FrequencyTicks)))
|
|
233
|
+
return self.Send(socketId, 'TimerSet(%s, %s)' % (TimerName, str(FrequencyTicks)))
|
|
237
234
|
|
|
238
235
|
# Reboot : Reboot the controller
|
|
239
236
|
def Reboot (self, socketId):
|
|
@@ -1345,6 +1342,19 @@ class XPS:
|
|
|
1345
1342
|
command = 'PositionerPositionCompareAquadBWindowedSet(' + PositionerName + ',' + str(MinimumPosition) + ',' + str(MaximumPosition) + ')'
|
|
1346
1343
|
return self.Send(socketId, command)
|
|
1347
1344
|
|
|
1345
|
+
# PositionerPositionCompareAquadBPrescalerSet: Sets PCO AquadB interpolation factor.
|
|
1346
|
+
def PositionerPositionCompareAquadBPrescalerSet(self, socketId, PositionerName, PCOInterpolationFactor):
|
|
1347
|
+
command = 'PositionerPositionCompareAquadBPrescalerSet(' + PositionerName + ',' + str(
|
|
1348
|
+
PCOInterpolationFactor) + ')'
|
|
1349
|
+
return self.Send(socketId, command)
|
|
1350
|
+
|
|
1351
|
+
# PositionerPositionCompareAquadBPrescalerGet : Gets PCO AquadB interpolation factor.
|
|
1352
|
+
def PositionerPositionCompareAquadBPrescalerGet(self, socketId, PositionerName):
|
|
1353
|
+
command = 'PositionerPositionCompareAquadBPrescalerGet(' + PositionerName + ',double *)'
|
|
1354
|
+
error, returnedString = self.Send(socketId, command)
|
|
1355
|
+
if (error != 0):
|
|
1356
|
+
return [error, returnedString]
|
|
1357
|
+
|
|
1348
1358
|
# PositionerPositionCompareGet : Read position compare parameters
|
|
1349
1359
|
def PositionerPositionCompareGet (self, socketId, PositionerName):
|
|
1350
1360
|
command = 'PositionerPositionCompareGet(' + PositionerName + ',double *,double *,double *,bool *)'
|
|
@@ -1601,6 +1611,11 @@ class XPS:
|
|
|
1601
1611
|
def MultipleAxesPVTVerification (self, socketId, GroupName, TrajectoryFileName):
|
|
1602
1612
|
command = 'MultipleAxesPVTVerification(' + GroupName + ',' + TrajectoryFileName + ')'
|
|
1603
1613
|
return self.Send(socketId, command)
|
|
1614
|
+
|
|
1615
|
+
# MultipleAxesPTVerification : Multiple axes PT trajectory verification
|
|
1616
|
+
def MultipleAxesPTVerification (self, socketId, GroupName, TrajectoryFileName):
|
|
1617
|
+
command = 'MultipleAxesPTVerification(' + GroupName + ',' + TrajectoryFileName + ')'
|
|
1618
|
+
return self.Send(socketId, command)
|
|
1604
1619
|
|
|
1605
1620
|
# MultipleAxesPVTVerificationResultGet : Multiple axes PVT trajectory verification result get
|
|
1606
1621
|
def MultipleAxesPVTVerificationResultGet (self, socketId, PositionerName):
|
|
@@ -1621,6 +1636,11 @@ class XPS:
|
|
|
1621
1636
|
def MultipleAxesPVTExecution (self, socketId, GroupName, TrajectoryFileName, ExecutionNumber):
|
|
1622
1637
|
command = 'MultipleAxesPVTExecution(' + GroupName + ',' + TrajectoryFileName + ',' + str(ExecutionNumber) + ')'
|
|
1623
1638
|
return self.Send(socketId, command)
|
|
1639
|
+
|
|
1640
|
+
# MultipleAxesPTExecution : Multiple axes PT trajectory execution
|
|
1641
|
+
def MultipleAxesPTExecution (self, socketId, GroupName, TrajectoryFileName, ExecutionNumber):
|
|
1642
|
+
command = 'MultipleAxesPTExecution(' + GroupName + ',' + TrajectoryFileName + ',' + str(ExecutionNumber) + ')'
|
|
1643
|
+
return self.Send(socketId, command)
|
|
1624
1644
|
|
|
1625
1645
|
# MultipleAxesPVTParametersGet : Multiple axes PVT trajectory get parameters
|
|
1626
1646
|
def MultipleAxesPVTParametersGet (self, socketId, GroupName):
|
|
@@ -2036,3 +2056,13 @@ class XPS:
|
|
|
2036
2056
|
# TestTCP : Test TCP/IP transfert
|
|
2037
2057
|
def TestTCP (self, socketId, InputString):
|
|
2038
2058
|
return self.Send(socketId, 'TestTCP(%s, char *)' % InputString)
|
|
2059
|
+
|
|
2060
|
+
# ========== Only for XPS-D ==========
|
|
2061
|
+
|
|
2062
|
+
# CleanCoreDumpFolder : Remove core file in /Admin/Public/CoreDump folder
|
|
2063
|
+
def CleanCoreDumpFolder (self, socketId):
|
|
2064
|
+
return self.Send(socketId, 'CleanCoreDumpFolder()')
|
|
2065
|
+
|
|
2066
|
+
# CleanTmpFolder : Clean the tmp folder
|
|
2067
|
+
def CleanTmpFolder(self, socketId):
|
|
2068
|
+
return self.Send(socketId, 'CleanTmpFolder()')
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
|
|
3
|
-
from __future__ import print_function
|
|
4
|
-
import os
|
|
5
3
|
import ftplib
|
|
6
|
-
import
|
|
7
|
-
from .utils import
|
|
4
|
+
from io import BytesIO
|
|
5
|
+
from .utils import str2bytes, bytes2str, ENCODING
|
|
8
6
|
|
|
9
7
|
import logging
|
|
10
8
|
logger = logging.getLogger('paramiko')
|
|
@@ -37,19 +35,19 @@ class FTPBaseWrapper(object):
|
|
|
37
35
|
self._conn.cwd(remotedir)
|
|
38
36
|
|
|
39
37
|
def connect(self, host=None, username=None, password=None):
|
|
40
|
-
raise
|
|
38
|
+
raise NotImplementedError
|
|
41
39
|
|
|
42
40
|
def save(self, remotefile, localfile):
|
|
43
41
|
"save remote file to local file"
|
|
44
|
-
raise
|
|
42
|
+
raise NotImplementedError
|
|
45
43
|
|
|
46
44
|
def getlines(self, remotefile):
|
|
47
45
|
"read text of remote file"
|
|
48
|
-
raise
|
|
46
|
+
raise NotImplementedError
|
|
49
47
|
|
|
50
48
|
def put(self, text, remotefile):
|
|
51
49
|
"put text to remote file"
|
|
52
|
-
raise
|
|
50
|
+
raise NotImplementedError
|
|
53
51
|
|
|
54
52
|
|
|
55
53
|
class SFTPWrapper(FTPBaseWrapper):
|
|
@@ -72,7 +70,7 @@ class SFTPWrapper(FTPBaseWrapper):
|
|
|
72
70
|
try:
|
|
73
71
|
self._conn = pysftp.Connection(self.host,
|
|
74
72
|
username=self.username,
|
|
75
|
-
password=self.
|
|
73
|
+
password=self.password)
|
|
76
74
|
except:
|
|
77
75
|
print("ERROR: sftp connection to %s failed" % self.host)
|
|
78
76
|
print("You may need to add the host keys for your XPS to your")
|
|
@@ -82,18 +80,18 @@ class SFTPWrapper(FTPBaseWrapper):
|
|
|
82
80
|
|
|
83
81
|
def save(self, remotefile, localfile):
|
|
84
82
|
"save remote file to local file"
|
|
85
|
-
self._conn.get(remotefile,
|
|
83
|
+
self._conn.get(remotefile, localfile)
|
|
86
84
|
|
|
87
85
|
def getlines(self, remotefile):
|
|
88
86
|
"read text of remote file"
|
|
89
|
-
tmp =
|
|
87
|
+
tmp = BytesIO()
|
|
90
88
|
self._conn.getfo(remotefile, tmp)
|
|
91
89
|
tmp.seek(0)
|
|
92
90
|
text = bytes2str(tmp.read())
|
|
93
91
|
return text.split('\n')
|
|
94
92
|
|
|
95
93
|
def put(self, text, remotefile):
|
|
96
|
-
txtfile =
|
|
94
|
+
txtfile = BytesIO(str2bytes(text))
|
|
97
95
|
self._conn.putfo(txtfile, remotefile)
|
|
98
96
|
|
|
99
97
|
|
|
@@ -116,14 +114,15 @@ class FTPWrapper(FTPBaseWrapper):
|
|
|
116
114
|
self._conn.connect(self.host)
|
|
117
115
|
self._conn.login(self.username, self.password)
|
|
118
116
|
|
|
117
|
+
def list(self):
|
|
118
|
+
"list files in a given directory (default the current)"
|
|
119
|
+
return self._conn.nlst()
|
|
120
|
+
|
|
119
121
|
def save(self, remotefile, localfile):
|
|
120
122
|
"save remote file to local file"
|
|
121
123
|
output = []
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if six.PY3:
|
|
125
|
-
open_opts['encoding'] = FTP_ENCODING
|
|
126
|
-
with open(localfile, 'w', **open_opts) as fout:
|
|
124
|
+
self._conn.retrbinary(f'RETR {remotefile}', output.append)
|
|
125
|
+
with open(localfile, 'w', encoding=ENCODING) as fout:
|
|
127
126
|
fout.write(''.join([bytes2str(s) for s in output]))
|
|
128
127
|
|
|
129
128
|
def getlines(self, remotefile):
|
|
@@ -134,6 +133,9 @@ class FTPWrapper(FTPBaseWrapper):
|
|
|
134
133
|
return text.split('\n')
|
|
135
134
|
|
|
136
135
|
def put(self, text, remotefile):
|
|
137
|
-
txtfile =
|
|
138
|
-
# print(" Put ", text, txtfile)
|
|
136
|
+
txtfile = BytesIO(str2bytes(text))
|
|
139
137
|
self._conn.storbinary('STOR %s' % remotefile, txtfile)
|
|
138
|
+
|
|
139
|
+
def delete(self, remotefile):
|
|
140
|
+
"delete remote file"
|
|
141
|
+
self._conn.delete(remotefile)
|
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
1
|
+
#!/usr/bin/env python
|
|
2
2
|
|
|
3
|
-
from __future__ import print_function
|
|
4
3
|
import os
|
|
5
4
|
import posixpath
|
|
6
5
|
import sys
|
|
7
6
|
import time
|
|
8
7
|
import socket
|
|
9
|
-
from
|
|
10
|
-
|
|
11
|
-
from six.moves import StringIO
|
|
12
|
-
from six.moves.configparser import ConfigParser
|
|
8
|
+
from io import StringIO
|
|
9
|
+
from configparser import ConfigParser
|
|
13
10
|
import numpy as np
|
|
14
11
|
|
|
12
|
+
from .debugtime import debugtime
|
|
13
|
+
from .utils import clean_text
|
|
15
14
|
from .XPS_C8_drivers import XPS, XPSException
|
|
16
|
-
|
|
17
15
|
from .ftp_wrapper import SFTPWrapper, FTPWrapper
|
|
18
16
|
|
|
19
17
|
IDLE, ARMING, ARMED, RUNNING, COMPLETE, WRITING, READING = \
|
|
@@ -31,6 +29,7 @@ def withConnectedXPS(fcn):
|
|
|
31
29
|
|
|
32
30
|
return wrapper
|
|
33
31
|
|
|
32
|
+
|
|
34
33
|
class NewportXPS:
|
|
35
34
|
gather_header = '# XPS Gathering Data\n#--------------'
|
|
36
35
|
def __init__(self, host, group=None,
|
|
@@ -38,11 +37,6 @@ class NewportXPS:
|
|
|
38
37
|
port=5001, timeout=10, extra_triggers=0,
|
|
39
38
|
outputs=('CurrentPosition', 'SetpointPosition')):
|
|
40
39
|
|
|
41
|
-
socket.setdefaulttimeout(5.0)
|
|
42
|
-
try:
|
|
43
|
-
host = socket.gethostbyname(host)
|
|
44
|
-
except:
|
|
45
|
-
raise ValueError('Could not resolve XPS name %s' % host)
|
|
46
40
|
self.host = host
|
|
47
41
|
self.port = port
|
|
48
42
|
self.username = username
|
|
@@ -57,8 +51,9 @@ class NewportXPS:
|
|
|
57
51
|
self.traj_file = None
|
|
58
52
|
self.traj_positioners = None
|
|
59
53
|
|
|
60
|
-
self.
|
|
61
|
-
self.
|
|
54
|
+
self.nsegments = -1
|
|
55
|
+
self.stages = {}
|
|
56
|
+
self.groups = {}
|
|
62
57
|
self.firmware_version = None
|
|
63
58
|
|
|
64
59
|
self.ftpconn = None
|
|
@@ -71,6 +66,9 @@ class NewportXPS:
|
|
|
71
66
|
if group is not None:
|
|
72
67
|
self.set_trajectory_group(group)
|
|
73
68
|
|
|
69
|
+
def __repr__(self):
|
|
70
|
+
return f"NewportXPS(host='{self.host}', port={self.port})"
|
|
71
|
+
|
|
74
72
|
@withConnectedXPS
|
|
75
73
|
def status_report(self):
|
|
76
74
|
"""return printable status report"""
|
|
@@ -113,7 +111,7 @@ class NewportXPS:
|
|
|
113
111
|
self.firmware_version = val
|
|
114
112
|
self.ftphome = ''
|
|
115
113
|
|
|
116
|
-
if 'XPS-D'
|
|
114
|
+
if any([m in self.firmware_version for m in ['XPS-D', 'HXP-D']]):
|
|
117
115
|
err, val = self._xps.Send(self._sid, 'InstallerVersionGet(char *)')
|
|
118
116
|
self.firmware_version = val
|
|
119
117
|
self.ftpconn = SFTPWrapper(**self.ftpargs)
|
|
@@ -128,7 +126,7 @@ class NewportXPS:
|
|
|
128
126
|
|
|
129
127
|
|
|
130
128
|
def check_error(self, err, msg='', with_raise=True):
|
|
131
|
-
if err
|
|
129
|
+
if err != 0:
|
|
132
130
|
err = "%d" % err
|
|
133
131
|
desc = self._xps.errorcodes.get(err, 'unknown error')
|
|
134
132
|
print("XPSError: message= %s, error=%s, description=%s" % (msg, err, desc))
|
|
@@ -165,19 +163,20 @@ class NewportXPS:
|
|
|
165
163
|
self.ftpconn.cwd(posixpath.join(self.ftphome, 'Config'))
|
|
166
164
|
lines = self.ftpconn.getlines('system.ini')
|
|
167
165
|
self.ftpconn.close()
|
|
166
|
+
initext = '\n'.join([line.strip() for line in lines])
|
|
168
167
|
|
|
169
168
|
pvtgroups = []
|
|
170
|
-
self.stages=
|
|
171
|
-
self.groups =
|
|
169
|
+
self.stages= {}
|
|
170
|
+
self.groups = {}
|
|
172
171
|
sconf = ConfigParser()
|
|
173
|
-
sconf.readfp(StringIO(
|
|
172
|
+
sconf.readfp(StringIO(initext))
|
|
174
173
|
|
|
175
174
|
# read and populate lists of groups first
|
|
176
175
|
for gtype, glist in sconf.items('GROUPS'): # ].items():
|
|
177
176
|
if len(glist) > 0:
|
|
178
177
|
for gname in glist.split(','):
|
|
179
178
|
gname = gname.strip()
|
|
180
|
-
self.groups[gname] =
|
|
179
|
+
self.groups[gname] = {}
|
|
181
180
|
self.groups[gname]['category'] = gtype.strip()
|
|
182
181
|
self.groups[gname]['positioners'] = []
|
|
183
182
|
if gtype.lower().startswith('multiple'):
|
|
@@ -236,9 +235,71 @@ class NewportXPS:
|
|
|
236
235
|
"""
|
|
237
236
|
self.ftpconn.connect(**self.ftpargs)
|
|
238
237
|
self.ftpconn.cwd(posixpath.join(self.ftphome, 'Public', 'Trajectories'))
|
|
239
|
-
self.ftpconn.put(text, filename)
|
|
238
|
+
self.ftpconn.put(clean_text(text), filename)
|
|
239
|
+
self.ftpconn.close()
|
|
240
|
+
|
|
241
|
+
def list_scripts(self):
|
|
242
|
+
"""list all existent scripts files
|
|
243
|
+
"""
|
|
244
|
+
remotefiles = ""
|
|
245
|
+
self.ftpconn.connect(**self.ftpargs)
|
|
246
|
+
self.ftpconn.cwd(posixpath.join(self.ftphome, 'Public', 'Scripts'))
|
|
247
|
+
remotefiles = self.ftpconn.list()
|
|
248
|
+
self.ftpconn.close()
|
|
249
|
+
|
|
250
|
+
return remotefiles
|
|
251
|
+
|
|
252
|
+
def read_script(self, filename):
|
|
253
|
+
"""read script content
|
|
254
|
+
|
|
255
|
+
Arguments:
|
|
256
|
+
----------
|
|
257
|
+
filename (str): name of script file
|
|
258
|
+
"""
|
|
259
|
+
filecontent = ""
|
|
260
|
+
self.ftpconn.connect(**self.ftpargs)
|
|
261
|
+
self.ftpconn.cwd(posixpath.join(self.ftphome, 'Public', 'Scripts'))
|
|
262
|
+
filecontent = self.ftpconn.getlines(filename)
|
|
263
|
+
self.ftpconn.close()
|
|
264
|
+
|
|
265
|
+
return filecontent
|
|
266
|
+
|
|
267
|
+
def download_script(self, filename):
|
|
268
|
+
"""download script file
|
|
269
|
+
|
|
270
|
+
Arguments:
|
|
271
|
+
----------
|
|
272
|
+
filename (str): name of script file
|
|
273
|
+
"""
|
|
274
|
+
self.ftpconn.connect(**self.ftpargs)
|
|
275
|
+
self.ftpconn.cwd(posixpath.join(self.ftphome, 'Public', 'Scripts'))
|
|
276
|
+
self.ftpconn.save(filename, filename)
|
|
277
|
+
self.ftpconn.close()
|
|
278
|
+
|
|
279
|
+
def upload_script(self, filename, text):
|
|
280
|
+
"""upload script file
|
|
281
|
+
|
|
282
|
+
Arguments:
|
|
283
|
+
----------
|
|
284
|
+
filename (str): name of script file
|
|
285
|
+
text (str): full text of script file
|
|
286
|
+
"""
|
|
287
|
+
self.ftpconn.connect(**self.ftpargs)
|
|
288
|
+
self.ftpconn.cwd(posixpath.join(self.ftphome, 'Public', 'Scripts'))
|
|
289
|
+
self.ftpconn.put(clean_text(text), filename)
|
|
240
290
|
self.ftpconn.close()
|
|
241
291
|
|
|
292
|
+
def delete_script(self, filename):
|
|
293
|
+
"""delete script file
|
|
294
|
+
|
|
295
|
+
Arguments:
|
|
296
|
+
----------
|
|
297
|
+
filename (str): name of script file
|
|
298
|
+
"""
|
|
299
|
+
self.ftpconn.connect(**self.ftpargs)
|
|
300
|
+
self.ftpconn.cwd(posixpath.join(self.ftphome, 'Public', 'Scripts'))
|
|
301
|
+
self.ftpconn.delete(filename)
|
|
302
|
+
self.ftpconn.close()
|
|
242
303
|
|
|
243
304
|
def upload_systemini(self, text):
|
|
244
305
|
"""upload text of system.ini
|
|
@@ -249,7 +310,7 @@ class NewportXPS:
|
|
|
249
310
|
"""
|
|
250
311
|
self.ftpconn.connect(**self.ftpargs)
|
|
251
312
|
self.ftpconn.cwd(posixpath.join(self.ftphome, 'Config'))
|
|
252
|
-
self.ftpconn.put(text, 'system.ini')
|
|
313
|
+
self.ftpconn.put(clean_text(text), 'system.ini')
|
|
253
314
|
self.ftpconn.close()
|
|
254
315
|
|
|
255
316
|
def upload_stagesini(self, text):
|
|
@@ -269,7 +330,7 @@ class NewportXPS:
|
|
|
269
330
|
"""
|
|
270
331
|
self.ftpconn.connect(**self.ftpargs)
|
|
271
332
|
self.ftpconn.cwd(posixpath.join(self.ftphome, 'Config'))
|
|
272
|
-
self.ftpconn.put(text, '
|
|
333
|
+
self.ftpconn.put(clean_text(text), 'stages.ini')
|
|
273
334
|
self.ftpconn.close()
|
|
274
335
|
|
|
275
336
|
@withConnectedXPS
|
|
@@ -413,8 +474,12 @@ class NewportXPS:
|
|
|
413
474
|
initialize all groups, no homing
|
|
414
475
|
"""
|
|
415
476
|
for g in self.groups:
|
|
416
|
-
|
|
417
|
-
|
|
477
|
+
try:
|
|
478
|
+
self.initialize_group(group=g)
|
|
479
|
+
except XPSException:
|
|
480
|
+
print(f"Warning: could not initialize '{g}' (already initialized?)")
|
|
481
|
+
|
|
482
|
+
|
|
418
483
|
def home_allgroups(self, with_encoder=True, home=False):
|
|
419
484
|
"""
|
|
420
485
|
home all groups
|
|
@@ -481,7 +546,7 @@ class NewportXPS:
|
|
|
481
546
|
"""
|
|
482
547
|
get dictionary of status for each group
|
|
483
548
|
"""
|
|
484
|
-
out =
|
|
549
|
+
out = {}
|
|
485
550
|
for group in self.groups:
|
|
486
551
|
err, stat = self._xps.GroupStatusGet(self._sid, group)
|
|
487
552
|
self.check_error(err, msg="GroupStatus '%s'" % (group))
|
|
@@ -497,7 +562,7 @@ class NewportXPS:
|
|
|
497
562
|
"""
|
|
498
563
|
get dictionary of hardware status for each stage
|
|
499
564
|
"""
|
|
500
|
-
out =
|
|
565
|
+
out = {}
|
|
501
566
|
for stage in self.stages:
|
|
502
567
|
if stage in ('', None): continue
|
|
503
568
|
err, stat = self._xps.PositionerHardwareStatusGet(self._sid, stage)
|
|
@@ -513,7 +578,7 @@ class NewportXPS:
|
|
|
513
578
|
"""
|
|
514
579
|
get dictionary of positioner errors for each stage
|
|
515
580
|
"""
|
|
516
|
-
out =
|
|
581
|
+
out = {}
|
|
517
582
|
for stage in self.stages:
|
|
518
583
|
if stage in ('', None): continue
|
|
519
584
|
err, stat = self._xps.PositionerErrorGet(self._sid, stage)
|
|
@@ -583,6 +648,18 @@ class NewportXPS:
|
|
|
583
648
|
vals.append(ret[i+1])
|
|
584
649
|
self._xps.GroupMoveAbsolute(self._sid, group, vals)
|
|
585
650
|
|
|
651
|
+
@withConnectedXPS
|
|
652
|
+
def execute_script(self, script, task, arguments):
|
|
653
|
+
"""
|
|
654
|
+
Execute a TCL script
|
|
655
|
+
|
|
656
|
+
Parameters:
|
|
657
|
+
script (string): name of script file
|
|
658
|
+
task (string): task name to be identified
|
|
659
|
+
arguments (string): script arguments
|
|
660
|
+
"""
|
|
661
|
+
self._xps.TCLScriptExecute(self._sid, script, task, arguments)
|
|
662
|
+
|
|
586
663
|
@withConnectedXPS
|
|
587
664
|
def move_stage(self, stage, value, relative=False):
|
|
588
665
|
"""
|
|
@@ -767,13 +844,11 @@ class NewportXPS:
|
|
|
767
844
|
for out in self.gather_outputs:
|
|
768
845
|
for i, ax in enumerate(traj['axes']):
|
|
769
846
|
outputs.append('%s.%s.%s' % (self.traj_group, ax, out))
|
|
770
|
-
# move_kws[ax] = float(traj['start'][i])
|
|
771
847
|
|
|
848
|
+
end_segment = traj['nsegments'] # - 1 + self.extra_triggers
|
|
849
|
+
self.nsegments = end_segment
|
|
772
850
|
|
|
773
|
-
end_segment = traj['nsegments'] - 1 + self.extra_triggers
|
|
774
|
-
# self.move_group(self.traj_group, **move_kws)
|
|
775
851
|
self.gather_titles = "%s\n#%s\n" % (self.gather_header, " ".join(outputs))
|
|
776
|
-
|
|
777
852
|
err, ret = self._xps.GatheringReset(self._sid)
|
|
778
853
|
self.check_error(err, msg="GatheringReset")
|
|
779
854
|
if verbose:
|
|
@@ -785,7 +860,7 @@ class NewportXPS:
|
|
|
785
860
|
if verbose:
|
|
786
861
|
print(" GatheringConfigurationSet outputs ", outputs)
|
|
787
862
|
print(" GatheringConfigurationSet returned ", ret)
|
|
788
|
-
print( end_segment, traj['pixeltime'])
|
|
863
|
+
print(" segments, pixeltime" , end_segment, traj['pixeltime'])
|
|
789
864
|
|
|
790
865
|
err, ret = self._xps.MultipleAxesPVTPulseOutputSet(self._sid, self.traj_group,
|
|
791
866
|
2, end_segment,
|
|
@@ -803,7 +878,7 @@ class NewportXPS:
|
|
|
803
878
|
self.traj_state = ARMED
|
|
804
879
|
|
|
805
880
|
@withConnectedXPS
|
|
806
|
-
def run_trajectory(self, name=None, save=True,
|
|
881
|
+
def run_trajectory(self, name=None, save=True, clean=False,
|
|
807
882
|
output_file='Gather.dat', verbose=False):
|
|
808
883
|
|
|
809
884
|
"""run a trajectory in PVT mode
|
|
@@ -811,8 +886,14 @@ class NewportXPS:
|
|
|
811
886
|
The trajectory *must be in the ARMED state
|
|
812
887
|
"""
|
|
813
888
|
|
|
814
|
-
if
|
|
815
|
-
self.
|
|
889
|
+
if 'xps-d' in self.firmware_version.lower():
|
|
890
|
+
self._xps.CleanTmpFolder(self._sid)
|
|
891
|
+
|
|
892
|
+
if clean:
|
|
893
|
+
self._xps.CleanCoreDumpFolder(self._sid)
|
|
894
|
+
|
|
895
|
+
if name in self.trajectories and self.traj_state != ARMED:
|
|
896
|
+
self.arm_trajectory(name, verbose=verbose)
|
|
816
897
|
|
|
817
898
|
if self.traj_state != ARMED:
|
|
818
899
|
raise XPSException("Must arm trajectory before running!")
|
|
@@ -851,41 +932,70 @@ class NewportXPS:
|
|
|
851
932
|
self.traj_state = COMPLETE
|
|
852
933
|
npulses = 0
|
|
853
934
|
if save:
|
|
854
|
-
self.read_and_save(output_file)
|
|
935
|
+
self.read_and_save(output_file, verbose=verbose)
|
|
855
936
|
self.traj_state = IDLE
|
|
856
937
|
return npulses
|
|
857
938
|
|
|
858
939
|
@withConnectedXPS
|
|
859
|
-
def read_and_save(self, output_file):
|
|
940
|
+
def read_and_save(self, output_file, verbose=False):
|
|
860
941
|
"read and save gathering file"
|
|
861
942
|
self.ngathered = 0
|
|
862
|
-
npulses, buff = self.read_gathering(set_idle_when_done=False
|
|
943
|
+
npulses, buff = self.read_gathering(set_idle_when_done=False,
|
|
944
|
+
verbose=verbose)
|
|
945
|
+
if npulses < 1:
|
|
946
|
+
return
|
|
863
947
|
self.save_gathering_file(output_file, buff,
|
|
864
|
-
verbose=
|
|
948
|
+
verbose=verbose,
|
|
865
949
|
set_idle_when_done=False)
|
|
866
950
|
self.ngathered = npulses
|
|
867
951
|
|
|
868
952
|
@withConnectedXPS
|
|
869
|
-
def read_gathering(self, set_idle_when_done=True,
|
|
953
|
+
def read_gathering(self, set_idle_when_done=True, verbose=False,
|
|
954
|
+
debug_time=False):
|
|
870
955
|
"""
|
|
871
956
|
read gathering data from XPS
|
|
872
957
|
"""
|
|
958
|
+
verbose = verbose or debug_time
|
|
959
|
+
if verbose:
|
|
960
|
+
print("READ Gathering XPS ", self.host, self._sid,
|
|
961
|
+
self.nsegments, time.ctime())
|
|
962
|
+
dt = debugtime()
|
|
873
963
|
self.traj_state = READING
|
|
874
|
-
|
|
964
|
+
npulses = -1
|
|
965
|
+
t0 = time.time()
|
|
966
|
+
while npulses < 1:
|
|
967
|
+
try:
|
|
968
|
+
ret, npulses, nx = self._xps.GatheringCurrentNumberGet(self._sid)
|
|
969
|
+
except SyntaxError:
|
|
970
|
+
print("#XPS Gathering Read failed, will try again")
|
|
971
|
+
pass
|
|
972
|
+
if time.time()-t0 > 5:
|
|
973
|
+
print("Failed to get gathering size after 5 seconds: return 0 points")
|
|
974
|
+
print("Gather Returned: ", ret, npulses, nx, self._xps, time.ctime())
|
|
975
|
+
return (0, ' \n')
|
|
976
|
+
if npulses < 1 or ret != 0:
|
|
977
|
+
time.sleep(0.05)
|
|
978
|
+
dt.add("gather num %d npulses=%d (%d)" % (ret, npulses, self.nsegments))
|
|
875
979
|
counter = 0
|
|
876
980
|
while npulses < 1 and counter < 5:
|
|
877
981
|
counter += 1
|
|
878
|
-
time.sleep(0.
|
|
982
|
+
time.sleep(0.25)
|
|
879
983
|
ret, npulses, nx = self._xps.GatheringCurrentNumberGet(self._sid)
|
|
880
984
|
print( 'Had to do repeat XPS Gathering: ', ret, npulses, nx)
|
|
881
|
-
|
|
985
|
+
dt.add("gather before multilinesget, npulses=%d" % (npulses))
|
|
986
|
+
try:
|
|
987
|
+
ret, buff = self._xps.GatheringDataMultipleLinesGet(self._sid, 0, npulses)
|
|
988
|
+
except ValueError:
|
|
989
|
+
print("Failed to read gathering: ", ret, buff)
|
|
990
|
+
return (0, ' \n')
|
|
991
|
+
dt.add("gather after multilinesget %d" % ret)
|
|
882
992
|
nchunks = -1
|
|
883
993
|
if ret < 0: # gathering too long: need to read in chunks
|
|
884
994
|
nchunks = 3
|
|
885
995
|
nx = int((npulses-2) / nchunks)
|
|
886
996
|
ret = 1
|
|
887
997
|
while True:
|
|
888
|
-
time.sleep(0.
|
|
998
|
+
time.sleep(0.05)
|
|
889
999
|
ret, xbuff = self._xps.GatheringDataMultipleLinesGet(self._sid, 0, nx)
|
|
890
1000
|
if ret == 0:
|
|
891
1001
|
break
|
|
@@ -902,23 +1012,25 @@ class NewportXPS:
|
|
|
902
1012
|
npulses-nchunks*nx)
|
|
903
1013
|
buff.append(xbuff)
|
|
904
1014
|
buff = ''.join(buff)
|
|
905
|
-
|
|
1015
|
+
dt.add("gather after got buffer %d" % len(buff))
|
|
906
1016
|
obuff = buff[:]
|
|
907
1017
|
for x in ';\r\t':
|
|
908
1018
|
obuff = obuff.replace(x,' ')
|
|
909
|
-
|
|
1019
|
+
dt.add("gather cleaned buffer %d" % len(obuff))
|
|
910
1020
|
if set_idle_when_done:
|
|
911
1021
|
self.traj_state = IDLE
|
|
1022
|
+
if verbose:
|
|
1023
|
+
dt.show()
|
|
912
1024
|
return npulses, obuff
|
|
913
1025
|
|
|
914
|
-
def save_gathering_file(self, fname,
|
|
1026
|
+
def save_gathering_file(self, fname, buff, verbose=False, set_idle_when_done=True):
|
|
915
1027
|
"""save gathering buffer read from read_gathering() to text file"""
|
|
916
1028
|
self.traj_state = WRITING
|
|
917
1029
|
f = open(fname, 'w')
|
|
918
1030
|
f.write(self.gather_titles)
|
|
919
|
-
f.write(
|
|
1031
|
+
f.write(buff)
|
|
920
1032
|
f.close()
|
|
921
|
-
nlines = len(
|
|
1033
|
+
nlines = len(buff.split('\n')) - 1
|
|
922
1034
|
if verbose:
|
|
923
1035
|
print('Wrote %i lines, %i bytes to %s' % (nlines, len(buff), fname))
|
|
924
1036
|
if set_idle_when_done:
|
|
@@ -1052,16 +1164,14 @@ class NewportXPS:
|
|
|
1052
1164
|
outputs.append('%s.%s.%s' % (self.traj_group, ax, out))
|
|
1053
1165
|
# move_kws[ax] = float(traj['start'][i])
|
|
1054
1166
|
|
|
1055
|
-
|
|
1056
1167
|
end_segment = traj['nsegments'] - 1 + self.extra_triggers
|
|
1057
1168
|
# self.move_group(self.traj_group, **move_kws)
|
|
1058
1169
|
self.gather_titles = "%s\n#%s\n" % (self.gather_header, " ".join(outputs))
|
|
1059
1170
|
|
|
1060
|
-
|
|
1061
1171
|
self._xps.GatheringReset(self._sid)
|
|
1062
1172
|
self._xps.GatheringConfigurationSet(self._sid, self.gather_outputs)
|
|
1063
1173
|
|
|
1064
|
-
print("step_number", step_number)
|
|
1174
|
+
# print("step_number", step_number)
|
|
1065
1175
|
ret = self._xps.MultipleAxesPVTPulseOutputSet(self._sid, self.traj_group,
|
|
1066
1176
|
2, step_number + 1, dtime)
|
|
1067
1177
|
ret = self._xps.MultipleAxesPVTVerification(self._sid, self.traj_group, traj_file)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
|
|
2
|
+
# it appears ftp really wants this encoding:
|
|
3
|
+
ENCODING = 'latin-1'
|
|
4
|
+
|
|
5
|
+
def bytes2str(s):
|
|
6
|
+
'byte to string conversion'
|
|
7
|
+
if isinstance(s, str):
|
|
8
|
+
return s
|
|
9
|
+
if isinstance(s, bytes):
|
|
10
|
+
return str(s, ENCODING)
|
|
11
|
+
else:
|
|
12
|
+
return str(s)
|
|
13
|
+
|
|
14
|
+
def str2bytes(s):
|
|
15
|
+
'string to bytes conversion'
|
|
16
|
+
if isinstance(s, bytes):
|
|
17
|
+
return s
|
|
18
|
+
if not isinstance(s, str):
|
|
19
|
+
s = str(s)
|
|
20
|
+
return s.encode(ENCODING)
|
|
21
|
+
|
|
22
|
+
def read_xps_file(fname):
|
|
23
|
+
"""read file written by XPS, decoding bytes as latin-1"""
|
|
24
|
+
with open(fname, 'rb') as fh:
|
|
25
|
+
data = fh.read()
|
|
26
|
+
return data.decode(ENCODING)
|
|
27
|
+
|
|
28
|
+
def clean_text(text):
|
|
29
|
+
buff = []
|
|
30
|
+
for line in text.split('\n'):
|
|
31
|
+
line = line.replace('\r', '').replace('\n', '') + ' '
|
|
32
|
+
buff.append(line)
|
|
33
|
+
return '\n'.join(buff)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: newportxps
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Python interface to Newport XPS motion controllers
|
|
5
|
+
Author-email: Matthew Newville <newville@cars.uchicago.edu>
|
|
6
|
+
License: BSD License
|
|
7
|
+
Keywords: motion control,data collection
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Education
|
|
11
|
+
Classifier: Intended Audience :: Other Audience
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: Implementation :: Jython
|
|
23
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
24
|
+
Classifier: Topic :: Education
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
27
|
+
Classifier: Topic :: Software Development
|
|
28
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
29
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
30
|
+
Classifier: Topic :: Utilities
|
|
31
|
+
Requires-Python: >=3.8
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
Requires-Dist: pysftp
|
|
35
|
+
|
|
36
|
+
# newportxps
|
|
37
|
+
|
|
38
|
+
This module provides code for using Newport XPS motor controllers from Python.
|
|
39
|
+
|
|
40
|
+
While Newport Corp. has provided a basic socket and ftp interface to the XPS
|
|
41
|
+
controller for a long time, this interface is very low-level. In addition,
|
|
42
|
+
there are some incompatibilities between the different generations of XPS
|
|
43
|
+
controllers (generations C, Q, D in that chronological order), and a lack of
|
|
44
|
+
support for Python 3 in the Newport-provided interface. The `newportxps`
|
|
45
|
+
module here aims to provide a simple, user-friendly interface for the Newport
|
|
46
|
+
XPS that works uniformly for all three generations of XPS and for both Python
|
|
47
|
+
2 and 3.
|
|
48
|
+
|
|
49
|
+
As an example, connecting to and reading the status of an XPS controller may
|
|
50
|
+
look like this:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
>>> from newportxps import NewportXPS
|
|
54
|
+
>>> xps = NewportXPS('164.54.160.000', username='Administrator', password='Please.Let.Me.In')
|
|
55
|
+
>>> print(xps.status_report())
|
|
56
|
+
# XPS host: 164.54.160.000 (164.54.160.000)
|
|
57
|
+
# Firmware: XPS-D-N13006
|
|
58
|
+
# Current Time: Sun Sep 16 13:40:24 2018
|
|
59
|
+
# Last Reboot: Wed Sep 12 14:46:44 2018
|
|
60
|
+
# Trajectory Group: None
|
|
61
|
+
# Groups and Stages
|
|
62
|
+
DetectorZ (SingleAxisInUse), Status: Ready state from motion
|
|
63
|
+
DetectorZ.Pos (ILS@ILS150CC@XPS-DRV11)
|
|
64
|
+
Hardware Status: First driver powered on - ZM low level
|
|
65
|
+
Positioner Errors: OK
|
|
66
|
+
SampleX (SingleAxisInUse), Status: Ready state from motion
|
|
67
|
+
SampleX.Pos (UTS@UTS150PP@XPS-DRV11)
|
|
68
|
+
Hardware Status: First driver powered on - ZM high level
|
|
69
|
+
Positioner Errors: OK
|
|
70
|
+
SampleY (SingleAxisInUse), Status: Ready state from motion
|
|
71
|
+
SampleY.Pos (UTS@UTS150PP@XPS-DRV11)
|
|
72
|
+
Hardware Status: First driver powered on - ZM high level
|
|
73
|
+
Positioner Errors: OK
|
|
74
|
+
SampleZ (SingleAxisInUse), Status: Ready state from motion
|
|
75
|
+
SampleZ.Pos (UTS@UTS150PP@XPS-DRV11)
|
|
76
|
+
Hardware Status: First driver powered on - ZM low level
|
|
77
|
+
Positioner Errors: OK
|
|
78
|
+
|
|
79
|
+
>>> for gname, info in xps.groups.items():
|
|
80
|
+
... print(gname, info)
|
|
81
|
+
...
|
|
82
|
+
DetectorX {'category': 'SingleAxisInUse', 'positioners': ['Pos']}
|
|
83
|
+
SampleX {'category': 'SingleAxisInUse', 'positioners': ['Pos']}
|
|
84
|
+
SampleY {'category': 'SingleAxisInUse', 'positioners': ['Pos']}
|
|
85
|
+
SampleZ {'category': 'SingleAxisInUse', 'positioners': ['Pos']}
|
|
86
|
+
>>>
|
|
87
|
+
>>> for sname, info in xps.stages.items():
|
|
88
|
+
... print(sname, xps.get_stage_position(sname), info)
|
|
89
|
+
...
|
|
90
|
+
DetectorX.Pos 36.5 {'type': 'ILS@ILS150CC@XPS-DRV11', 'max_velo': 100, 'max_accel': 400, 'low_limit': -74, 'high_limit': 74}
|
|
91
|
+
SampleX.Pos 1.05 {'type': 'UTS@UTS150PP@XPS-DRV11', 'max_velo': 20, 'max_accel': 80, 'low_limit': -74, 'high_limit': 74}
|
|
92
|
+
SampleY.Pos 0.24 {'type': 'UTS@UTS150PP@XPS-DRV11', 'max_velo': 20, 'max_accel': 80, 'low_limit': -74, 'high_limit': 74}
|
|
93
|
+
SampleZ.Pos 2.5 {'type': 'UTS@UTS150PP@XPS-DRV11', 'max_velo': 20, 'max_accel': 80, 'low_limit': -74, 'high_limit': 74}
|
|
94
|
+
|
|
95
|
+
>>> xps.move_stage('SampleZ.Pos', 1.0)
|
|
96
|
+
|
|
97
|
+
>>> xps.home_group('DetectorX')
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
On creation and initialization of the NewportXPS, the Groups and status of the
|
|
103
|
+
controller are read in and Stages defined so that they can be queried or
|
|
104
|
+
moved.
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
The `NewportXPS` class has a number of methods to interact with the controller including:
|
|
108
|
+
|
|
109
|
+
* reboot controller
|
|
110
|
+
* get status, hardware errors, etc.
|
|
111
|
+
* save and upload new `system.ini` and `stages.ini` files.
|
|
112
|
+
* enable and disable Groups.
|
|
113
|
+
* initialize and home Stages and Groups of Stages.
|
|
114
|
+
* read Stage positions.
|
|
115
|
+
* move Stages and Groups to new positions.
|
|
116
|
+
* set Stage velocity.
|
|
117
|
+
* define simple linear trajectories (using PVT mode), both 'forward' and 'backward'.
|
|
118
|
+
* upload any PVT trajectory.
|
|
119
|
+
* arm PVT trajectory.
|
|
120
|
+
* run PVT trajectory.
|
|
121
|
+
* read and save Gathering file for a trajectory.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "newportxps"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{name="Matthew Newville", email="newville@cars.uchicago.edu"},
|
|
10
|
+
]
|
|
11
|
+
description = "Python interface to Newport XPS motion controllers"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.8"
|
|
14
|
+
keywords = ["motion control", "data collection"]
|
|
15
|
+
|
|
16
|
+
license = {text = "BSD License"}
|
|
17
|
+
dependencies = ['pysftp']
|
|
18
|
+
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 5 - Production/Stable",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Intended Audience :: Education",
|
|
23
|
+
"Intended Audience :: Other Audience",
|
|
24
|
+
"Intended Audience :: Science/Research",
|
|
25
|
+
"License :: OSI Approved :: BSD License",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Programming Language :: Python",
|
|
28
|
+
"Programming Language :: Python :: 3",
|
|
29
|
+
"Programming Language :: Python :: 3.8",
|
|
30
|
+
"Programming Language :: Python :: 3.9",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
"Programming Language :: Python :: 3.12",
|
|
34
|
+
"Programming Language :: Python :: Implementation :: Jython",
|
|
35
|
+
"Programming Language :: Python :: Implementation :: PyPy",
|
|
36
|
+
"Topic :: Education",
|
|
37
|
+
"Topic :: Scientific/Engineering",
|
|
38
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
39
|
+
"Topic :: Software Development",
|
|
40
|
+
"Topic :: Software Development :: Libraries",
|
|
41
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
42
|
+
"Topic :: Utilities"
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
newportxps-0.2/PKG-INFO
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import six
|
|
3
|
-
from six.moves import StringIO as bytesio
|
|
4
|
-
|
|
5
|
-
# it appears ftp really wants this encoding:
|
|
6
|
-
FTP_ENCODING = 'latin-1'
|
|
7
|
-
|
|
8
|
-
def bytes2str(s):
|
|
9
|
-
return str(s)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if six.PY3:
|
|
13
|
-
from io import BytesIO as bytesio
|
|
14
|
-
|
|
15
|
-
def bytes2str(s):
|
|
16
|
-
'byte to string conversion'
|
|
17
|
-
if isinstance(s, str):
|
|
18
|
-
return s
|
|
19
|
-
elif isinstance(s, bytes):
|
|
20
|
-
return str(s, FTP_ENCODING)
|
|
21
|
-
else:
|
|
22
|
-
return str(s)
|
newportxps-0.2/setup.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
|
|
3
|
-
from setuptools import setup
|
|
4
|
-
|
|
5
|
-
setup(name = 'newportxps',
|
|
6
|
-
version = '0.2',
|
|
7
|
-
author = 'Matthew Newville',
|
|
8
|
-
author_email = 'newville@cars.uchicago.edu',
|
|
9
|
-
license = 'BSD',
|
|
10
|
-
description = 'Python interface to Newport XPS controllers',
|
|
11
|
-
packages = ['newportxps'],
|
|
12
|
-
install_requires = ['pysftp']
|
|
13
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|