newportxps 0.9__tar.gz → 2026.1.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.
Files changed (31) hide show
  1. newportxps-2026.1.0/LICENSE +21 -0
  2. {newportxps-0.9/newportxps.egg-info → newportxps-2026.1.0}/PKG-INFO +9 -9
  3. {newportxps-0.9 → newportxps-2026.1.0}/newportxps/XPS_C8_drivers.py +3 -2
  4. {newportxps-0.9 → newportxps-2026.1.0}/newportxps/__init__.py +1 -0
  5. {newportxps-0.9 → newportxps-2026.1.0}/newportxps/ftp_wrapper.py +37 -13
  6. {newportxps-0.9 → newportxps-2026.1.0}/newportxps/newportxps.py +128 -83
  7. newportxps-2026.1.0/newportxps/version.py +34 -0
  8. newportxps-2026.1.0/newportxps/xps_main.py +125 -0
  9. {newportxps-0.9 → newportxps-2026.1.0/newportxps.egg-info}/PKG-INFO +9 -9
  10. {newportxps-0.9 → newportxps-2026.1.0}/newportxps.egg-info/SOURCES.txt +2 -0
  11. newportxps-2026.1.0/newportxps.egg-info/entry_points.txt +2 -0
  12. newportxps-2026.1.0/newportxps.egg-info/requires.txt +3 -0
  13. {newportxps-0.9 → newportxps-2026.1.0}/pyproject.toml +9 -7
  14. newportxps-0.9/LICENSE +0 -25
  15. newportxps-0.9/newportxps/version.py +0 -16
  16. newportxps-0.9/newportxps.egg-info/requires.txt +0 -2
  17. {newportxps-0.9 → newportxps-2026.1.0}/.gitignore +0 -0
  18. {newportxps-0.9 → newportxps-2026.1.0}/README.md +0 -0
  19. {newportxps-0.9 → newportxps-2026.1.0}/examples/stages_XPSC.ini +0 -0
  20. {newportxps-0.9 → newportxps-2026.1.0}/examples/stages_XPSD.ini +0 -0
  21. {newportxps-0.9 → newportxps-2026.1.0}/examples/system_exA.ini +0 -0
  22. {newportxps-0.9 → newportxps-2026.1.0}/examples/system_exB.ini +0 -0
  23. {newportxps-0.9 → newportxps-2026.1.0}/examples/system_exC.ini +0 -0
  24. {newportxps-0.9 → newportxps-2026.1.0}/examples/system_exD.ini +0 -0
  25. {newportxps-0.9 → newportxps-2026.1.0}/newportxps/debugtime.py +0 -0
  26. {newportxps-0.9 → newportxps-2026.1.0}/newportxps/debugtimer.py +0 -0
  27. {newportxps-0.9 → newportxps-2026.1.0}/newportxps/utils.py +0 -0
  28. {newportxps-0.9 → newportxps-2026.1.0}/newportxps.egg-info/dependency_links.txt +0 -0
  29. {newportxps-0.9 → newportxps-2026.1.0}/newportxps.egg-info/top_level.txt +0 -0
  30. {newportxps-0.9 → newportxps-2026.1.0}/setup.cfg +0 -0
  31. {newportxps-0.9 → newportxps-2026.1.0}/setup.py +0 -0
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2025 Matthew Newville, The University of Chicago
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXP80RESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE qUSE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: newportxps
3
- Version: 0.9
3
+ Version: 2026.1.0
4
4
  Summary: Python interface to Newport XPS motion controllers
5
5
  Author-email: Matthew Newville <newville@cars.uchicago.edu>
6
- License: BSD License
6
+ License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/pyepics/newportxps/
8
8
  Keywords: motion control,data collection
9
9
  Classifier: Development Status :: 5 - Production/Stable
@@ -11,16 +11,14 @@ Classifier: Intended Audience :: Developers
11
11
  Classifier: Intended Audience :: Education
12
12
  Classifier: Intended Audience :: Other Audience
13
13
  Classifier: Intended Audience :: Science/Research
14
- Classifier: License :: OSI Approved :: BSD License
15
14
  Classifier: Operating System :: OS Independent
16
15
  Classifier: Programming Language :: Python
17
16
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.8
19
- Classifier: Programming Language :: Python :: 3.9
20
17
  Classifier: Programming Language :: Python :: 3.10
21
18
  Classifier: Programming Language :: Python :: 3.11
22
19
  Classifier: Programming Language :: Python :: 3.12
23
- Classifier: Programming Language :: Python :: Implementation :: Jython
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
24
22
  Classifier: Programming Language :: Python :: Implementation :: PyPy
25
23
  Classifier: Topic :: Education
26
24
  Classifier: Topic :: Scientific/Engineering
@@ -29,11 +27,13 @@ Classifier: Topic :: Software Development
29
27
  Classifier: Topic :: Software Development :: Libraries
30
28
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
31
29
  Classifier: Topic :: Utilities
32
- Requires-Python: >=3.8
30
+ Requires-Python: >=3.10
33
31
  Description-Content-Type: text/markdown
34
32
  License-File: LICENSE
35
- Requires-Dist: pysftp
33
+ Requires-Dist: paramiko
36
34
  Requires-Dist: numpy
35
+ Requires-Dist: tabulate
36
+ Dynamic: license-file
37
37
 
38
38
  # newportxps
39
39
 
@@ -91,6 +91,7 @@ class XPS:
91
91
  # print("SEND REC ", command, type(command))
92
92
  suffix = ',EndOfAPI'
93
93
  try:
94
+ XPS.__sockets[socketId].settimeout(3600.0)
94
95
  XPS.__sockets[socketId].send(str2bytes(command))
95
96
  ret = bytes2str(XPS.__sockets[socketId].recv(1024))
96
97
  while (ret.find(suffix) == -1):
@@ -1139,7 +1140,7 @@ class XPS:
1139
1140
  def MultipleAxesPVTVerification (self, socketId, GroupName, TrajectoryFileName):
1140
1141
  command = 'MultipleAxesPVTVerification(' + GroupName + ',' + TrajectoryFileName + ')'
1141
1142
  return self.Send(socketId, command)
1142
-
1143
+
1143
1144
  # MultipleAxesPTVerification : Multiple axes PT trajectory verification
1144
1145
  def MultipleAxesPTVerification (self, socketId, GroupName, TrajectoryFileName):
1145
1146
  command = 'MultipleAxesPTVerification(' + GroupName + ',' + TrajectoryFileName + ')'
@@ -1156,7 +1157,7 @@ class XPS:
1156
1157
  def MultipleAxesPVTExecution (self, socketId, GroupName, TrajectoryFileName, ExecutionNumber):
1157
1158
  command = 'MultipleAxesPVTExecution(' + GroupName + ',' + TrajectoryFileName + ',' + str(ExecutionNumber) + ')'
1158
1159
  return self.Send(socketId, command)
1159
-
1160
+
1160
1161
  # MultipleAxesPTExecution : Multiple axes PT trajectory execution
1161
1162
  def MultipleAxesPTExecution (self, socketId, GroupName, TrajectoryFileName, ExecutionNumber):
1162
1163
  command = 'MultipleAxesPTExecution(' + GroupName + ',' + TrajectoryFileName + ',' + str(ExecutionNumber) + ')'
@@ -1,3 +1,4 @@
1
1
  from .version import __version__, __version_tuple__
2
2
 
3
3
  from .newportxps import NewportXPS
4
+ from .xps_main import xps_main
@@ -9,6 +9,15 @@ logger = logging.getLogger('paramiko')
9
9
  logger.setLevel(logging.ERROR)
10
10
 
11
11
 
12
+ SFTP_ERROR_MESSAGE = """Could not connect to XPS with sftp: no host key.
13
+
14
+ You may need to add a host key to your `ssh known_hosts` file, using
15
+ ssh-keyscan {host} >> ~/.ssh/known_hosts
16
+
17
+ or first connecting with `sftp Administrator@{host}` """
18
+
19
+ import paramiko
20
+
12
21
  HAS_PYSFTP = False
13
22
  try:
14
23
  import pysftp
@@ -53,7 +62,9 @@ class FTPBaseWrapper(object):
53
62
  class SFTPWrapper(FTPBaseWrapper):
54
63
  """wrap ftp interactions for Newport XPS models D"""
55
64
  def __init__(self, host=None, username='Administrator',
56
- password='Administrator'):
65
+ password='Administrator', use_paramiko=True):
66
+ self.use_paramiko = use_paramiko
67
+ self.ssh_client = None
57
68
  FTPBaseWrapper.__init__(self, host=host,
58
69
  username=username, password=password)
59
70
 
@@ -64,19 +75,32 @@ class SFTPWrapper(FTPBaseWrapper):
64
75
  self.username = username
65
76
  if password is not None:
66
77
  self.password = password
78
+ if not self.use_paramiko and HAS_PYSFTP:
79
+ self._conn = pysftp.Connection(host,
80
+ username=username,
81
+ password=password)
82
+ else:
83
+ if self.ssh_client is None:
84
+ self.ssh_client = paramiko.SSHClient()
85
+ self.ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
86
+
87
+ try:
88
+ self.ssh_client.connect(host, 22, username, password)
89
+
90
+ except paramiko.AuthenticationException:
91
+ print("Authentication failed. Check your username and password/key.")
92
+ raise ValueError(SFTP_ERROR_MESSAGE.format(host=self.host))
93
+ except paramiko.SSHException as e:
94
+ print(f"SSH connection error: {e}")
95
+ raise ValueError(SFTP_ERROR_MESSAGE.format(host=self.host))
96
+ finally:
97
+ self._conn = self.ssh_client.open_sftp()
67
98
 
68
- if not HAS_PYSFTP:
69
- raise ValueError("pysftp not installed.")
70
- try:
71
- self._conn = pysftp.Connection(self.host,
72
- username=self.username,
73
- password=self.password)
74
- except:
75
- print("ERROR: sftp connection to %s failed" % self.host)
76
- print("You may need to add the host keys for your XPS to your")
77
- print("ssh known_hosts file, using a command like this:")
78
- print(" ssh-keyscan %s >> ~/.ssh/known_hosts" % self.host)
79
-
99
+ def cwd(self, remotedir):
100
+ if self.use_paramiko:
101
+ self._conn.chdir(remotedir)
102
+ elif hasattr(self._conn, 'cwd'):
103
+ self._conn.cwd(remotedir)
80
104
 
81
105
  def save(self, remotefile, localfile):
82
106
  "save remote file to local file"
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
 
3
3
  import posixpath
4
+ import atexit
4
5
  import sys
5
6
  import time
6
7
  import socket
@@ -28,7 +29,6 @@ def withConnectedXPS(fcn):
28
29
 
29
30
  return wrapper
30
31
 
31
-
32
32
  class NewportXPS:
33
33
  gather_header = '# XPS Gathering Data\n#--------------'
34
34
  def __init__(self, host, group=None,
@@ -88,19 +88,26 @@ class NewportXPS:
88
88
 
89
89
  for groupname, status in self.get_group_status().items():
90
90
  this = self.groups[groupname]
91
- out.append(f"{groupname} ({this['category']}), Status: {status}")
91
+ out.append(f"#\n# {groupname} ({this['category']}), Status: {status}")
92
92
  for pos in this['positioners']:
93
93
  stagename = f"{groupname}.{pos}"
94
94
  stage = self.stages[stagename]
95
- out.extend([f"# {stagename} ({stage['stagetype']})",
95
+ out.extend([f" {stagename} ({stage['stagetype']})",
96
96
  f" Hardware Status: {hstat[stagename]}",
97
97
  f" Positioner Errors: {perrs[stagename]}"])
98
98
  return "\n".join(out)
99
99
 
100
+ def disconnect(self):
101
+ self.ftpconn.close()
102
+ if self._sid is not None:
103
+ self._xps.TCP_CloseSocket(self._sid)
104
+ self._sid = None
100
105
 
101
106
  def connect(self):
102
107
  self._sid = self._xps.TCP_ConnectToServer(self.host,
103
108
  self.port, self.timeout)
109
+
110
+ atexit.register(self.disconnect)
104
111
  try:
105
112
  self._xps.Login(self._sid, self.username, self.password)
106
113
  except:
@@ -109,8 +116,7 @@ class NewportXPS:
109
116
  err, val = self._xps.FirmwareVersionGet(self._sid)
110
117
  self.firmware_version = val
111
118
  self.ftphome = ''
112
-
113
- if any([m in self.firmware_version for m in ['XPS-D', 'HXP-D']]):
119
+ if any([m in self.firmware_version for m in ['XPS-D', 'HXP-D', 'XPS-RL']]):
114
120
  err, val = self._xps.Send(self._sid, 'InstallerVersionGet(char *)')
115
121
  self.firmware_version = val
116
122
  self.ftpconn = SFTPWrapper(**self.ftpargs)
@@ -120,6 +126,11 @@ class NewportXPS:
120
126
  self.ftphome = '/Admin'
121
127
  self.read_systemini()
122
128
 
129
+ def clean_folders(self):
130
+ if 'xps-d' in self.firmware_version.lower():
131
+ self._xps.CleanTmpFolder(self._sid)
132
+ self._xps.CleanCoreDumpFolder(self._sid)
133
+
123
134
 
124
135
  def check_error(self, err, msg='', with_raise=True):
125
136
  if err != 0:
@@ -168,7 +179,7 @@ class NewportXPS:
168
179
  sconf.read_string(initext)
169
180
 
170
181
  # read and populate lists of groups first
171
- for gtype, glist in sconf.items('GROUPS'): # ].items():
182
+ for gtype, glist in sconf.items('GROUPS'):
172
183
  if len(glist) > 0:
173
184
  for gname in glist.split(','):
174
185
  gname = gname.strip()
@@ -191,21 +202,19 @@ class NewportXPS:
191
202
 
192
203
  if len(pvtgroups) == 1:
193
204
  self.set_trajectory_group(pvtgroups[0])
194
-
195
205
  for sname in self.stages:
196
206
  ret = self._xps.PositionerMaximumVelocityAndAccelerationGet(self._sid, sname)
197
207
  try:
198
208
  self.stages[sname]['max_velo'] = ret[1]
199
- self.stages[sname]['max_accel'] = ret[2]/3.0
209
+ self.stages[sname]['max_accel'] = 0.75*ret[2]
200
210
  except:
201
- print(f"could not set max velo/accel for {name}")
211
+ print(f"could not read max velo/accel for {sname}")
202
212
  ret = self._xps.PositionerUserTravelLimitsGet(self._sid, sname)
203
213
  try:
204
214
  self.stages[sname]['low_limit'] = ret[1]
205
215
  self.stages[sname]['high_limit'] = ret[2]
206
216
  except:
207
- print(f"could not set limits for {sname}")
208
-
217
+ print(f"could not read limits for {sname}")
209
218
  return self.groups
210
219
 
211
220
  def download_trajectory(self, filename):
@@ -426,8 +435,8 @@ class NewportXPS:
426
435
  print(f"Warning: could not enable trajectory group '{self.traj_group}'")
427
436
  return
428
437
 
429
- for i in range(64):
430
- self._xps.EventExtendedRemove(self._sid, i)
438
+ #for i in range(64):
439
+ # self._xps.EventExtendedRemove(self._sid, i)
431
440
 
432
441
  # build template for linear trajectory file:
433
442
  trajline1 = ['%(ramptime)f']
@@ -630,8 +639,6 @@ class NewportXPS:
630
639
  print("Do have a group to move")
631
640
  return
632
641
  ret = self._xps.GroupMoveAbort(self._sid, group)
633
- print('abort group ', group, ret)
634
-
635
642
 
636
643
  @withConnectedXPS
637
644
  def move_group(self, group=None, **kws):
@@ -640,7 +647,7 @@ class NewportXPS:
640
647
  if group is None or group not in self.groups:
641
648
  group = self.traj_group
642
649
  if group is None:
643
- print("Do have a group to move")
650
+ print("no group to move")
644
651
  return
645
652
  posnames = [p.lower() for p in self.groups[group]['positioners']]
646
653
  ret = self._xps.GroupPositionCurrentGet(self._sid, group, len(posnames))
@@ -742,8 +749,8 @@ class NewportXPS:
742
749
 
743
750
 
744
751
  @withConnectedXPS
745
- def define_line_trajectories(self, axis, group=None, pixeltime=0.01,
746
- scantime=None, start=0, stop=1, step=0.001,
752
+ def define_line_trajectories(self, axis, group=None, pixeltime=None,
753
+ scantime=None, start=0, stop=1, step=0.01,
747
754
  accel=None, upload=True, verbose=False):
748
755
  """defines 'forward' and 'backward' trajectories for a simple
749
756
  single element line scan using PVT Mode
@@ -753,15 +760,14 @@ class NewportXPS:
753
760
 
754
761
  if self.traj_group is None:
755
762
  raise XPSException("No trajectory group defined")
756
-
763
+ # print(f"Define Line traj {axis=}, {self.traj_group=}")
757
764
  for axname in (axis, axis.upper(), axis.lower(), axis.title()):
758
765
  stage = f"{self.traj_group}.{axname}"
759
766
  if stage in self.stages:
760
767
  break
761
768
 
762
- # print(" Stage ", stage, self.stages[stage])
763
- max_velo = 0.75*self.stages[stage]['max_velo']
764
- max_accel = 0.5*self.stages[stage]['max_accel']
769
+ max_velo = self.stages[stage]['max_velo']
770
+ max_accel = self.stages[stage]['max_accel']
765
771
 
766
772
  if accel is None:
767
773
  accel = max_accel
@@ -773,20 +779,24 @@ class NewportXPS:
773
779
  step = scandir*abs(step)
774
780
 
775
781
  npulses = int((abs(stop - start) + abs(step)*1.1) / abs(step))
776
- if pixeltime is None and scantime is not None:
777
- scantime = float(abs(scantime))
778
- pixeltime= scantime / (npulses-1)
779
- scantime = pixeltime*npulses
782
+ if pixeltime is None:
783
+ if scantime is None:
784
+ raise ValueError("line trajectory must set pixeltime or scantime")
785
+ else:
786
+ pixeltime = float(abs(scantime))/(npulses-1)
787
+ scantime = float(abs(pixeltime))*npulses
780
788
 
781
789
  distance = (abs(stop - start) + abs(step))*1.0
782
790
  velocity = min(distance/scantime, max_velo)
783
-
784
- ramptime = max(2.e-5, abs(velocity/accel))
785
- rampdist = velocity*ramptime
786
- offset = step/2.0 + scandir*rampdist
791
+ if verbose:
792
+ print(f"trajecory: {scantime=:.5f}, {pixeltime=:.5f}, {npulses=}, {start=:.5f}, {stop=:.5f}, {step=:.5f}")
793
+ ramptime = max(5.e-4, abs(velocity/accel))
794
+ rampdist = max(5.e-6, 0.5*velocity*ramptime)
795
+ offset = 0.5*step + scandir*rampdist
787
796
 
788
797
  trajbase = {'axes': [axis],
789
798
  'type': 'line',
799
+ 'group': self.traj_group,
790
800
  'pixeltime': pixeltime, 'uploaded': False,
791
801
  'npulses': npulses+1, 'nsegments': 3}
792
802
 
@@ -828,6 +838,8 @@ class NewportXPS:
828
838
  self.linear_template % back)
829
839
  self.trajectories['foreward']['uploaded'] = True
830
840
  self.trajectories['backward']['uploaded'] = True
841
+ self.trajectories['foreward']['text'] = self.linear_template % fore
842
+ self.trajectories['backward']['text'] = self.linear_template % back
831
843
  ret = True
832
844
  except:
833
845
  raise ValueError("error uploading trajectory")
@@ -835,7 +847,7 @@ class NewportXPS:
835
847
 
836
848
  @withConnectedXPS
837
849
  def define_array_trajectory(self, positions, dtime=1.0, max_accels=None,
838
- upload=True, name='array', verbose=True):
850
+ upload=True, name='array', group=None, verbose=True):
839
851
  """define a PVT trajectory for the trajectory group from a dictionary of
840
852
  position arrays for each positioner in the trajectory group.
841
853
 
@@ -847,6 +859,7 @@ class NewportXPS:
847
859
  dtime: float, time per segment
848
860
  max_accels: dict of {PosName: max_acceleration} to use.
849
861
  name: name of trajectory (file will be f"{name}.trj")
862
+ group: name of trajectory group
850
863
  upload: bool, whether to upload trajectory
851
864
 
852
865
  Returns:
@@ -868,6 +881,8 @@ class NewportXPS:
868
881
  decelerate to zero velocity.
869
882
 
870
883
  """
884
+ if group is not None:
885
+ self.traj_group = group
871
886
  tgroup = self.traj_group
872
887
  if tgroup is None:
873
888
  raise XPSException("No trajectory group defined")
@@ -951,9 +966,9 @@ class NewportXPS:
951
966
  velo[axes] = np.zeros(npulses+1, dtype=np.float64)
952
967
  accel[axes] = np.zeros(npulses+1, dtype=np.float64)
953
968
 
954
-
955
969
  traj = {'axes': all_axes,
956
970
  'type': 'array',
971
+ 'group': self.traj_group,
957
972
  'start': start, 'pixeltime': dtime,
958
973
  'npulses': npulses+1, 'nsegments': npulses+1,
959
974
  'uploaded': False}
@@ -966,7 +981,7 @@ class NewportXPS:
966
981
  p, v = pos[axes][n], velo[axes][n]
967
982
  line.extend([f"{p:.8f}", f"{v:.8f}"])
968
983
  buff.append(', '.join(line))
969
-
984
+ buff.append('')
970
985
  buff = '\n'.join(buff)
971
986
  traj['pvt_buffer'] = buff
972
987
 
@@ -982,20 +997,32 @@ class NewportXPS:
982
997
  return traj
983
998
 
984
999
 
1000
+ def get_trajectory(self, name, verify_group=True):
1001
+ """get defined trajectory by name, with error checking"""
1002
+ traj = self.trajectories.get(name, None)
1003
+ if traj is None:
1004
+ raise XPSException(f"Cannot find trajectory named '{name}'")
1005
+
1006
+ if verify_group:
1007
+ traj_group = traj.get('group', self.traj_group)
1008
+ if traj_group != self.traj_group:
1009
+ raise XPSException(f"trajectory '{name}' uses group '{traj_group}', not '{self.traj_group=}'")
1010
+ return traj
1011
+
985
1012
  @withConnectedXPS
986
- def move_to_trajectory_start(self, name):
1013
+ def move_to_trajectory_start(self, name, group=None):
987
1014
  """
988
1015
  move to the start position of a named trajectory
989
1016
  """
990
- tgroup = self.traj_group
991
- if tgroup is None:
992
- raise XPSException("No trajectory group defined")
1017
+ if group is not None:
1018
+ self.traj_group = group
993
1019
 
994
- traj = self.trajectories.get(name, None)
995
- if traj is None:
996
- raise XPSException(f"Cannot find trajectory named '{name}'")
1020
+ if self.traj_group is None:
1021
+ raise XPSException("No trajectory group defined")
997
1022
 
1023
+ traj = self.get_trajectory(name, verify_group=True)
998
1024
  if traj['type'] == 'line':
1025
+ tgroup = traj['group']
999
1026
  for pos, axes in zip(traj['start'], traj['axes']):
1000
1027
  self.move_stage(f'{tgroup}.{axes}', pos)
1001
1028
 
@@ -1004,30 +1031,26 @@ class NewportXPS:
1004
1031
  if pos is not None:
1005
1032
  self.move_stage(f'{tgroup}.{axes}', pos)
1006
1033
 
1007
-
1008
-
1009
1034
  @withConnectedXPS
1010
- def arm_trajectory(self, name, verbose=False, move_to_start=True):
1035
+ def arm_trajectory(self, name, verbose=False, move_to_start=True, group=None):
1011
1036
  """
1012
1037
  prepare to run a named (assumed uploaded) trajectory
1013
1038
  """
1039
+ if group is not None:
1040
+ self.traj_group = group
1041
+
1014
1042
  if self.traj_group is None:
1015
1043
  print("Must set group name!")
1016
1044
 
1017
- traj = self.trajectories.get(name, None)
1018
- if traj is None:
1019
- raise XPSException(f"Cannot find trajectory '{name}'")
1020
-
1045
+ traj = self.get_trajectory(name, verify_group=True)
1021
1046
  if not traj['uploaded']:
1022
1047
  raise XPSException(f"trajectory '{name}' has not been uploaded")
1023
1048
 
1024
-
1025
1049
  self.traj_state = ARMING
1026
1050
  self.traj_file = f'{name}.trj'
1027
1051
 
1028
1052
  if move_to_start:
1029
1053
  self.move_to_trajectory_start(name)
1030
-
1031
1054
  # move_kws = {}
1032
1055
  outputs = []
1033
1056
  for out in self.gather_outputs:
@@ -1049,33 +1072,33 @@ class NewportXPS:
1049
1072
 
1050
1073
  if verbose:
1051
1074
  print(" GatheringConfigurationSet outputs ", outputs)
1052
- print(" GatheringConfigurationSet returned ", ret)
1075
+ print(" GatheringConfigurationSet returned ", ret, time.ctime())
1053
1076
  print(" segments, pixeltime" , end_segment, traj['pixeltime'])
1054
1077
 
1055
1078
  err, ret = self._xps.MultipleAxesPVTPulseOutputSet(self._sid, self.traj_group,
1056
1079
  2, end_segment,
1057
1080
  traj['pixeltime'])
1058
- self.check_error(err, msg="PVTPulseOutputSet", with_raise=False)
1081
+ self.check_error(err, msg="PVTPulseOutputSet", with_raise=True)
1059
1082
  if verbose:
1060
- print(" PVTPulse ", ret)
1083
+ print(" PVTPulse ", ret, time.ctime())
1061
1084
  err, ret = self._xps.MultipleAxesPVTVerification(self._sid,
1062
1085
  self.traj_group,
1063
1086
  self.traj_file)
1064
1087
 
1065
- self.check_error(err, msg="PVTVerification", with_raise=False)
1088
+ self.check_error(err, msg="PVTVerification", with_raise=True)
1066
1089
  if verbose:
1067
- print(" PVTVerify ", ret)
1090
+ print(" PVTVerify ", ret, time.ctime())
1068
1091
  self.traj_state = ARMED
1069
1092
 
1070
1093
  @withConnectedXPS
1071
- def run_trajectory(self, name=None, save=True, clean=False,
1072
- output_file='Gather.dat', verbose=False):
1094
+ def run_trajectory(self, name=None, save=True, clean=False, group=None,
1095
+ output_file='Gather.dat', verbose=False, move_to_start=True):
1073
1096
 
1074
1097
  """run a trajectory in PVT mode
1075
1098
 
1076
1099
  The trajectory *must be in the ARMED state
1077
1100
  """
1078
-
1101
+ dt = debugtime()
1079
1102
  if 'xps-d' in self.firmware_version.lower():
1080
1103
  self._xps.CleanTmpFolder(self._sid)
1081
1104
 
@@ -1083,11 +1106,13 @@ class NewportXPS:
1083
1106
  self._xps.CleanCoreDumpFolder(self._sid)
1084
1107
 
1085
1108
  if name in self.trajectories and self.traj_state != ARMED:
1086
- self.arm_trajectory(name, verbose=verbose)
1109
+ self.arm_trajectory(name, verbose=verbose, group=group,
1110
+ move_to_start=move_to_start)
1087
1111
 
1088
1112
  if self.traj_state != ARMED:
1089
1113
  raise XPSException("Must arm trajectory before running!")
1090
1114
 
1115
+ dt.add('armed')
1091
1116
  tgroup = self.traj_group
1092
1117
  buffer = ('Always', f'{tgroup}.PVT.TrajectoryPulse',)
1093
1118
  err, ret = self._xps.EventExtendedConfigurationTriggerSet(self._sid, buffer,
@@ -1096,35 +1121,46 @@ class NewportXPS:
1096
1121
  self.check_error(err, msg="EventConfigTrigger")
1097
1122
  if verbose:
1098
1123
  print( " EventExtended Trigger Set ", ret)
1099
-
1124
+ dt.add('event trigger set')
1100
1125
  err, ret = self._xps.EventExtendedConfigurationActionSet(self._sid,
1101
1126
  ('GatheringOneData',),
1102
1127
  ('',), ('',),('',),('',))
1103
1128
  self.check_error(err, msg="EventConfigAction")
1104
1129
  if verbose:
1105
1130
  print( " EventExtended Action Set ", ret)
1106
-
1131
+ dt.add('event action set')
1107
1132
  eventID, m = self._xps.EventExtendedStart(self._sid)
1108
1133
  self.traj_state = RUNNING
1109
1134
 
1110
1135
  if verbose:
1111
1136
  print( " EventExtended ExtendedStart ", eventID, m)
1112
-
1137
+ dt.add('event start')
1113
1138
  err, ret = self._xps.MultipleAxesPVTExecution(self._sid,
1114
1139
  self.traj_group,
1115
1140
  self.traj_file, 1)
1116
1141
  self.check_error(err, msg="PVT Execute", with_raise=False)
1117
1142
  if verbose:
1118
- print( " PVT Execute ", ret)
1119
-
1143
+ print( " PVT Execute done ", ret)
1144
+ dt.add('pvt execute')
1120
1145
  ret = self._xps.EventExtendedRemove(self._sid, eventID)
1121
1146
  ret = self._xps.GatheringStop(self._sid)
1122
-
1147
+ dt.add('gathering stop')
1123
1148
  self.traj_state = COMPLETE
1124
1149
  npulses = 0
1125
1150
  if save:
1126
- self.read_and_save(output_file, verbose=verbose)
1151
+ npulses, buff = self.read_gathering(set_idle_when_done=False,
1152
+ verbose=verbose)
1153
+ dt.add('read gathering')
1154
+ if npulses > 0:
1155
+ self.save_gathering_file(output_file, buff,
1156
+ verbose=verbose,
1157
+ set_idle_when_done=False)
1158
+ dt.add('saved gathering')
1159
+ self.ngathered = npulses
1160
+ # self.read_and_save(output_file, verbose=verbose)
1127
1161
  self.traj_state = IDLE
1162
+ if verbose:
1163
+ dt.show()
1128
1164
  return npulses
1129
1165
 
1130
1166
  @withConnectedXPS
@@ -1156,16 +1192,18 @@ class NewportXPS:
1156
1192
  t0 = time.time()
1157
1193
  while npulses < 1:
1158
1194
  try:
1159
- ret, npulses, nx = self._xps.GatheringCurrentNumberGet(self._sid)
1195
+ gdat = self._xps.GatheringCurrentNumberGet(self._sid)
1160
1196
  except SyntaxError:
1161
1197
  print("#XPS Gathering Read failed, will try again")
1162
- pass
1198
+ if len(gdat) == 3:
1199
+ ret, npulses, nx = gdat
1200
+ if npulses < 1 or ret != 0:
1201
+ time.sleep(0.1)
1202
+
1163
1203
  if time.time()-t0 > 5:
1164
1204
  print("Failed to get gathering size after 5 seconds: return 0 points")
1165
1205
  print("Gather Returned: ", ret, npulses, nx, self._xps, time.ctime())
1166
1206
  return (0, ' \n')
1167
- if npulses < 1 or ret != 0:
1168
- time.sleep(0.05)
1169
1207
  dt.add("gather num %d npulses=%d (%d)" % (ret, npulses, self.nsegments))
1170
1208
  counter = 0
1171
1209
  while npulses < 1 and counter < 5:
@@ -1177,7 +1215,7 @@ class NewportXPS:
1177
1215
  try:
1178
1216
  ret, buff = self._xps.GatheringDataMultipleLinesGet(self._sid, 0, npulses)
1179
1217
  except ValueError:
1180
- print("Failed to read gathering: ", ret, buff)
1218
+ print("Failed to read gathering: ", ret)
1181
1219
  return (0, ' \n')
1182
1220
  dt.add("gather after multilinesget %d" % ret)
1183
1221
  nchunks = -1
@@ -1231,11 +1269,17 @@ class NewportXPS:
1231
1269
  start_values=None,
1232
1270
  stop_values=None,
1233
1271
  accel_values=None,
1234
- pulse_time=0.1, scan_time=10.0):
1272
+ pulse_time=0.1, scan_time=10.0, group=None):
1235
1273
  """
1236
1274
  Clemens' code for line trajectories -- should probably be
1237
1275
  unified with define_line_trajectories(),
1238
1276
  """
1277
+ if group is not None:
1278
+ self.traj_group = group
1279
+
1280
+ if self.traj_group is None:
1281
+ raise XPSException("No trajectory group defined")
1282
+
1239
1283
  if start_values is None:
1240
1284
  start_values = np.zeros(len(self.traj_positioners))
1241
1285
  else:
@@ -1264,7 +1308,6 @@ class NewportXPS:
1264
1308
 
1265
1309
  ramp_time = 1.5 * max(abs(velocities / accel_values))
1266
1310
  ramp = velocities * ramp_time
1267
- print("ramp : ", ramp_time, ramp)
1268
1311
 
1269
1312
  ramp_attr = {'ramptime': ramp_time}
1270
1313
  down_attr = {'ramptime': ramp_time}
@@ -1302,6 +1345,7 @@ class NewportXPS:
1302
1345
  trajectory_str += down_str + '\n'
1303
1346
 
1304
1347
  self.trajectories[name] = {'pulse_time': pulse_time,
1348
+ 'group': self.traj_group,
1305
1349
  'step_number': len(distances)}
1306
1350
 
1307
1351
  for ind, positioner in enumerate(self.traj_positioners):
@@ -1315,11 +1359,15 @@ class NewportXPS:
1315
1359
  return trajectory_str
1316
1360
 
1317
1361
  def run_line_trajectory_general(self, name='default', verbose=False, save=True,
1318
- outfile='Gather.dat'):
1362
+ outfile='Gather.dat', group=None):
1319
1363
  """run trajectory in PVT mode"""
1320
- traj = self.trajectories.get(name, None)
1321
- if traj is None:
1322
- raise XPSException(f'Cannot find trajectory named {name}')
1364
+ if group is not None:
1365
+ self.traj_group = group
1366
+
1367
+ if self.traj_group is None:
1368
+ raise XPSException("No trajectory group defined")
1369
+
1370
+ traj = self.get_trajectory(name, verify_group=True)
1323
1371
 
1324
1372
  traj_file = f'{name}.trj'
1325
1373
  dtime = traj['pulse_time']
@@ -1337,9 +1385,8 @@ class NewportXPS:
1337
1385
 
1338
1386
  outputs = []
1339
1387
  for out in self.gather_outputs:
1340
- for i, ax in enumerate(traj['axes']):
1388
+ for ax in traj['axes']:
1341
1389
  outputs.append(f'{self.traj_group}.{ax}.{out}')
1342
- # move_kws[ax] = float(traj['start'][i])
1343
1390
 
1344
1391
  o = " ".join(outputs)
1345
1392
  self.gather_titles = f"{self.gather_header}\n#{o}\n"
@@ -1350,7 +1397,7 @@ class NewportXPS:
1350
1397
  2, step_number + 1, dtime)
1351
1398
  self.check_error(err, msg="MultipleAxesPVTPulseOutputSet", with_raise=False)
1352
1399
 
1353
- err, ret = self._xps.MultipleAxesPVTVerification(self._sid, self.traj_group, traj_file)
1400
+ err, _ = self._xps.MultipleAxesPVTVerification(self._sid, self.traj_group, traj_file)
1354
1401
  self.check_error(err, msg="MultipleAxesPVTVerification", with_raise=False)
1355
1402
 
1356
1403
  buffer = ('Always', self.traj_group + '.PVT.TrajectoryPulse')
@@ -1363,7 +1410,7 @@ class NewportXPS:
1363
1410
  ('',), ('',), ('',), ('',))
1364
1411
  self.check_error(err, msg="EventExtendedConfigurationActionSet", with_raise=False)
1365
1412
 
1366
- eventID, m = self._xps.EventExtendedStart(self._sid)
1413
+ eventID, _ = self._xps.EventExtendedStart(self._sid)
1367
1414
 
1368
1415
  self._xps.MultipleAxesPVTExecution(self._sid, self.traj_group, traj_file, 1)
1369
1416
  self._xps.EventExtendedRemove(self._sid, eventID)
@@ -1371,15 +1418,13 @@ class NewportXPS:
1371
1418
 
1372
1419
  npulses = 0
1373
1420
  if save:
1374
- npulses, outbuff = self.read_and_save(outfile)
1421
+ npulses, _ = self.read_and_save(outfile)
1375
1422
 
1376
1423
  self._xps.GroupMoveRelative(self._sid, self.traj_group, ramps)
1377
1424
  return npulses
1378
1425
 
1379
1426
 
1380
-
1381
1427
  if __name__ == '__main__':
1382
- import sys
1383
1428
  ipaddr = sys.argv[1]
1384
1429
  x = NewportXPS(ipaddr)
1385
1430
  x.read_systemini()
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '2026.1.0'
32
+ __version_tuple__ = version_tuple = (2026, 1, 0)
33
+
34
+ __commit_id__ = commit_id = 'g3f4cc44e3'
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env python
2
+
3
+ from pathlib import Path
4
+ from argparse import ArgumentParser
5
+ from tabulate import tabulate
6
+
7
+ from .newportxps import NewportXPS
8
+
9
+ HELP_MESSAGE = """xps: simple interaction with NewportXPS controllers
10
+ xps -h shows this message.
11
+ xps [ADDR] status print status and configuration for XPS
12
+ xps [ADDR] groups print list of groups
13
+ xps [ADDR] reboot reboot xps
14
+ xps [ADDR] initialize [GROUP] initialize group by name
15
+ xps [ADDR] initialize_all initialize all group
16
+ xps [ADDR] home [GROUP] home group by name
17
+ xps [ADDR] home_all home all groups
18
+ xps [ADDR] get_system_ini [FILE] download system.ini to file
19
+ xps [ADDR] put_system_ini [FILE] upload system.ini from file
20
+ xps [ADDR] get_stages_ini [FILE] download stages.ini to file
21
+ xps [ADDR] put_stages_ini [FILE] upload stages.ini from file
22
+
23
+ ADDR: name or ip address for the controller
24
+ FILE: name of file to save or read
25
+ GROUP: name of group to initialize or home
26
+ """
27
+
28
+ def xps_main():
29
+ parser = ArgumentParser(prog='xps', description='NewportXPS controllers',
30
+ add_help=False)
31
+ parser.add_argument('-h', '--help', dest='help', action='store_true',
32
+ default=False, help='show help')
33
+ parser.add_argument('options', nargs='*')
34
+ args = parser.parse_args()
35
+
36
+ if args.help or len(args.options) == 0:
37
+ print(HELP_MESSAGE)
38
+ return
39
+
40
+ ipaddr = args.options.pop(0)
41
+ command = args.options.pop(0)
42
+ _argu = ''
43
+ if len(args.options) > 0:
44
+ _argu = args.options.pop(0)
45
+
46
+ try:
47
+ this_xps = NewportXPS(ipaddr)
48
+ except XPSException:
49
+ print(f"cannot connect to NewportXPS at {ipaddr=}")
50
+ return
51
+ except Exception:
52
+ print(f"unknown error connecting to NewportXPS at {ipaddr=}")
53
+ return
54
+
55
+ if command == 'status':
56
+ print(this_xps.status_report())
57
+ elif command == 'groups':
58
+ headers =('Group Name', 'Positioners', 'Type')
59
+ dat = []
60
+ for gn, gd in this_xps.groups.items():
61
+ dat.append((gn, ', '.join(gd['positioners']), gd['category']))
62
+ print(tabulate(dat, headers))
63
+ elif command == 'initialize_all':
64
+ this_xps.initialize_allgroups()
65
+ elif command == 'initialize':
66
+ if len(_argu) < 1:
67
+ print("xps initialize needs a group name, or use `xps initialize_all`")
68
+ return
69
+ groupname = _argu
70
+ if groupname not in this_xps.groups.keys():
71
+ print(f"xps initialize needs a valid group name, one of {', '.join(this_xps.groups.keys())}")
72
+ return
73
+ this_xps.initialize_group(groupname)
74
+ elif command == 'home_all':
75
+ this_xps.homee_allgroups()
76
+ elif command == 'home':
77
+ if len(_argu) < 1:
78
+ print("xps home needs a group name, or use `xps home_all`")
79
+ return
80
+ groupname = _argu
81
+ if groupname not in this_xps.groups.keys():
82
+ print(f"xps home needs a valid group name, one of {', '.join(this_xps.groups.keys())}")
83
+ return
84
+ this_xps.home_group(groupname)
85
+
86
+ elif command == 'reboot':
87
+ print(f"rebooting {ipaddr}")
88
+ this_xps.reboot()
89
+
90
+ elif command == 'get_system_ini':
91
+ filename = _argu
92
+ if len(filename) < 1:
93
+ filename = f'system_{ipaddr}.ini'
94
+ this_xps.save_systemini(filename)
95
+ print(f"saved system.ini to {filename}")
96
+
97
+ elif command == 'get_stages_ini':
98
+ filename = _argu
99
+ if len(filename) < 1:
100
+ filename = f'stages_{ipaddr}.ini'
101
+ this_xps.save_stagesini(filename)
102
+ print(f"saved stages.ini to {filename}")
103
+
104
+ elif command == 'put_system_ini':
105
+ filename = _argu
106
+ if len(filename) < 1:
107
+ print("xps put_system_ini needs system.ini file")
108
+ return
109
+ text = open(filneme, 'r').read()
110
+ this_xps.upload_systemini(text)
111
+ print(f"uploaded text from {filename} as system.ini")
112
+
113
+ elif command == 'put_stages_ini':
114
+ filename = _argu
115
+ if len(filename) < 1:
116
+ print("xps put_stages_ini needs stages.ini file")
117
+ return
118
+ text = open(filneme, 'r').read()
119
+ this_xps.upload_stagesini(text)
120
+ print(f"uploaded text from {filename} as stages.ini")
121
+
122
+
123
+
124
+ if __name__ == '__main__':
125
+ xps_main()
@@ -1,9 +1,9 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: newportxps
3
- Version: 0.9
3
+ Version: 2026.1.0
4
4
  Summary: Python interface to Newport XPS motion controllers
5
5
  Author-email: Matthew Newville <newville@cars.uchicago.edu>
6
- License: BSD License
6
+ License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/pyepics/newportxps/
8
8
  Keywords: motion control,data collection
9
9
  Classifier: Development Status :: 5 - Production/Stable
@@ -11,16 +11,14 @@ Classifier: Intended Audience :: Developers
11
11
  Classifier: Intended Audience :: Education
12
12
  Classifier: Intended Audience :: Other Audience
13
13
  Classifier: Intended Audience :: Science/Research
14
- Classifier: License :: OSI Approved :: BSD License
15
14
  Classifier: Operating System :: OS Independent
16
15
  Classifier: Programming Language :: Python
17
16
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.8
19
- Classifier: Programming Language :: Python :: 3.9
20
17
  Classifier: Programming Language :: Python :: 3.10
21
18
  Classifier: Programming Language :: Python :: 3.11
22
19
  Classifier: Programming Language :: Python :: 3.12
23
- Classifier: Programming Language :: Python :: Implementation :: Jython
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
24
22
  Classifier: Programming Language :: Python :: Implementation :: PyPy
25
23
  Classifier: Topic :: Education
26
24
  Classifier: Topic :: Scientific/Engineering
@@ -29,11 +27,13 @@ Classifier: Topic :: Software Development
29
27
  Classifier: Topic :: Software Development :: Libraries
30
28
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
31
29
  Classifier: Topic :: Utilities
32
- Requires-Python: >=3.8
30
+ Requires-Python: >=3.10
33
31
  Description-Content-Type: text/markdown
34
32
  License-File: LICENSE
35
- Requires-Dist: pysftp
33
+ Requires-Dist: paramiko
36
34
  Requires-Dist: numpy
35
+ Requires-Dist: tabulate
36
+ Dynamic: license-file
37
37
 
38
38
  # newportxps
39
39
 
@@ -17,8 +17,10 @@ newportxps/ftp_wrapper.py
17
17
  newportxps/newportxps.py
18
18
  newportxps/utils.py
19
19
  newportxps/version.py
20
+ newportxps/xps_main.py
20
21
  newportxps.egg-info/PKG-INFO
21
22
  newportxps.egg-info/SOURCES.txt
22
23
  newportxps.egg-info/dependency_links.txt
24
+ newportxps.egg-info/entry_points.txt
23
25
  newportxps.egg-info/requires.txt
24
26
  newportxps.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ xps = newportxps:xps_main
@@ -0,0 +1,3 @@
1
+ paramiko
2
+ numpy
3
+ tabulate
@@ -16,13 +16,14 @@ authors = [
16
16
  {name="Matthew Newville", email="newville@cars.uchicago.edu"},
17
17
  ]
18
18
 
19
- dependencies = ['pysftp', 'numpy']
19
+ dependencies = ['paramiko', 'numpy', 'tabulate']
20
20
  description = "Python interface to Newport XPS motion controllers"
21
21
  readme = "README.md"
22
- requires-python = ">=3.8"
22
+ requires-python = ">=3.10"
23
23
  keywords = ["motion control", "data collection"]
24
24
 
25
- license = {text = "BSD License"}
25
+ license = "MIT"
26
+ license-files = ["LICENSE"]
26
27
 
27
28
  classifiers = [
28
29
  "Development Status :: 5 - Production/Stable",
@@ -30,16 +31,14 @@ classifiers = [
30
31
  "Intended Audience :: Education",
31
32
  "Intended Audience :: Other Audience",
32
33
  "Intended Audience :: Science/Research",
33
- "License :: OSI Approved :: BSD License",
34
34
  "Operating System :: OS Independent",
35
35
  "Programming Language :: Python",
36
36
  "Programming Language :: Python :: 3",
37
- "Programming Language :: Python :: 3.8",
38
- "Programming Language :: Python :: 3.9",
39
37
  "Programming Language :: Python :: 3.10",
40
38
  "Programming Language :: Python :: 3.11",
41
39
  "Programming Language :: Python :: 3.12",
42
- "Programming Language :: Python :: Implementation :: Jython",
40
+ "Programming Language :: Python :: 3.13",
41
+ "Programming Language :: Python :: 3.14",
43
42
  "Programming Language :: Python :: Implementation :: PyPy",
44
43
  "Topic :: Education",
45
44
  "Topic :: Scientific/Engineering",
@@ -53,3 +52,6 @@ classifiers = [
53
52
 
54
53
  [project.urls]
55
54
  Homepage = "https://github.com/pyepics/newportxps/"
55
+
56
+ [project.scripts]
57
+ xps = "newportxps:xps_main"
newportxps-0.9/LICENSE DELETED
@@ -1,25 +0,0 @@
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.
@@ -1,16 +0,0 @@
1
- # file generated by setuptools_scm
2
- # don't change, don't track in version control
3
- TYPE_CHECKING = False
4
- if TYPE_CHECKING:
5
- from typing import Tuple, Union
6
- VERSION_TUPLE = Tuple[Union[int, str], ...]
7
- else:
8
- VERSION_TUPLE = object
9
-
10
- version: str
11
- __version__: str
12
- __version_tuple__: VERSION_TUPLE
13
- version_tuple: VERSION_TUPLE
14
-
15
- __version__ = version = '0.9'
16
- __version_tuple__ = version_tuple = (0, 9)
@@ -1,2 +0,0 @@
1
- pysftp
2
- numpy
File without changes
File without changes
File without changes
File without changes