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.
@@ -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 = OrderedDict()
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(six.b(command))
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 = OrderedDict()
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 six
7
- from .utils import six, bytes2str, bytesio, FTP_ENCODING
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 NotImplemented
38
+ raise NotImplementedError
41
39
 
42
40
  def save(self, remotefile, localfile):
43
41
  "save remote file to local file"
44
- raise NotImplemented
42
+ raise NotImplementedError
45
43
 
46
44
  def getlines(self, remotefile):
47
45
  "read text of remote file"
48
- raise NotImplemented
46
+ raise NotImplementedError
49
47
 
50
48
  def put(self, text, remotefile):
51
49
  "put text to remote file"
52
- raise NotImplemented
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.username)
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, remotefile)
83
+ self._conn.get(remotefile, localfile)
86
84
 
87
85
  def getlines(self, remotefile):
88
86
  "read text of remote file"
89
- tmp = bytesio()
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 = bytesio(six.b(text))
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
- x = self._conn.retrbinary('RETR %s' % remotefile, output.append)
123
- open_opts = {}
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 = bytesio(six.b(text))
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 collections import OrderedDict
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.stages = OrderedDict()
61
- self.groups = OrderedDict()
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' in self.firmware_version:
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 is not 0:
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= OrderedDict()
171
- self.groups = OrderedDict()
169
+ self.stages= {}
170
+ self.groups = {}
172
171
  sconf = ConfigParser()
173
- sconf.readfp(StringIO('\n'.join(lines)))
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] = OrderedDict()
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, 'system.ini')
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
- self.initialize_group(group=g)
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 = OrderedDict()
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 = OrderedDict()
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 = OrderedDict()
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 name in self.trajectories:
815
- self.arm_trajectory(name)
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=False,
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, debug_time=False):
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
- ret, npulses, nx = self._xps.GatheringCurrentNumberGet(self._sid)
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.5)
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
- ret, buff = self._xps.GatheringDataMultipleLinesGet(self._sid, 0, npulses)
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.1)
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, buffer, verbose=False, set_idle_when_done=True):
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(buffer)
1031
+ f.write(buff)
920
1032
  f.close()
921
- nlines = len(buffer.split('\n')) - 1
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.
@@ -1,4 +1,6 @@
1
+ LICENSE
1
2
  README.md
3
+ pyproject.toml
2
4
  setup.py
3
5
  newportxps/XPS_C8_drivers.py
4
6
  newportxps/__init__.py
@@ -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
+
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env python
2
+
3
+ import setuptools
4
+
5
+ if __name__ == "__main__":
6
+ setuptools.setup()
newportxps-0.2/PKG-INFO DELETED
@@ -1,10 +0,0 @@
1
- Metadata-Version: 1.0
2
- Name: newportxps
3
- Version: 0.2
4
- Summary: Python interface to Newport XPS controllers
5
- Home-page: UNKNOWN
6
- Author: Matthew Newville
7
- Author-email: newville@cars.uchicago.edu
8
- License: BSD
9
- Description: UNKNOWN
10
- Platform: UNKNOWN
@@ -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)
@@ -1,10 +0,0 @@
1
- Metadata-Version: 1.0
2
- Name: newportxps
3
- Version: 0.2
4
- Summary: Python interface to Newport XPS controllers
5
- Home-page: UNKNOWN
6
- Author: Matthew Newville
7
- Author-email: newville@cars.uchicago.edu
8
- License: BSD
9
- Description: UNKNOWN
10
- Platform: UNKNOWN
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