enhance syncronization -> parameters in setup_sync

-> sp.setup_sync(verbose=args.verbose&0x40,timeOfs=0.03,timeCor=0.0005)
- cleanup code
- add motion markers
- add debug data
- rework triggerSync
- BUT nothing should change in Sync
This commit is contained in:
2022-10-04 16:39:13 +02:00
parent 7a968aac96
commit 20c6d690e8
6 changed files with 438 additions and 202 deletions

View File

@@ -7,6 +7,8 @@
# *-----------------------------------------------------------------------*
'''
Coord[1].Q[0] : sync points when using setup_sync(mode=2)
-3: when armed (at start position)
-2: triggerSync waiting for start trigger
0: after setup_sync(mode=2) call
idx: index of position during run
-1: pvt motion is finished
@@ -44,7 +46,7 @@ class MotionBase:
self.meta={'srv_per':ServoPeriod,'pt2pt_time':40,'sync_flag':0,'sync_mode':2}
self.meta.update(kwargs)
def setup_sync(self, crdId=1, prgId=2,verbose=False, timeOfs=0.):
def setup_sync(self, crdId=1, prgId=2,verbose=False, timeOfs=0.,timeCor=0.):
'''setup the timing synchronization for the motion program
kwargs:
sync_mode : default=2
@@ -88,41 +90,43 @@ class MotionBase:
flag0='Coord[{crdId}].Q[10]'.format(crdId=crdId) if sync_flag&1 else 'Gate3[1].Chan[0].UserFlag'
flag1='Coord[{crdId}].Q[11]'.format(crdId=crdId) if sync_flag&2 else 'Gate3[1].Chan[1].UserFlag'
if sync_mode==1:
prg = '''
Coord[{crdId}].Q[0]=-2
Coord[{crdId}].TimeBaseSlew=1 //1E-4 is default
Coord[{crdId}].DesTimeBase=0
while({flag0}==0){{}}
Coord[{crdId}].Q[0]=-1
Gather.Enable=2
while({flag1}==0){{}}
Coord[1].DesTimeBase=Sys.ServoPeriod
'''.format(crdId=crdId, flag0=flag0, flag1=flag1)
prg = f'\n{flag0}=0\n' if sync_flag&1 else ''
prg += f'''
Coord[{crdId}].Q[0]=-2 motion program started and armed
Coord[{crdId}].TimeBaseSlew=1 //1E-4 is default
Coord[{crdId}].DesTimeBase=0
while({flag0}==0){{}}
Coord[{crdId}].Q[0]=-1
Gather.Enable=2
while({flag1}==0){{}}
Coord[1].DesTimeBase=Sys.ServoPeriod
'''
else:
prg = '''
//Gather.Enable=2 is done in the sync program
Coord[1].TimeBaseSlew=1 //1E-4 is default
Coord[1].DesTimeBase=0
//Coord[1].Q[0]=-1 is done in the sync program
'''.format(crdId=crdId, flag0=flag0, flag1=flag1)
prg = f'''
//Gather.Enable=2 is done in the triggerSync program
Coord[1].TimeBaseSlew=1 //1E-4 is default
Coord[1].DesTimeBase=0
Coord[1].Q[0]=-3 //motion program started and waits. Arm(-2) is done in triggerSync program
'''
self.sync_prg = prg
#download and start triggerSync code
comm=self.comm
if comm is None: return
arg=0 #argument to pass to triggerSync program
if sync_mode==2:arg+=1 #synchronize
arg+=sync_flag*2 #simulated or real triggers
if verbose: arg+=8
if sync_mode==2 or arg&4:
mode=0 #mode to pass to triggerSync program
if sync_mode==2:mode+=1 #synchronize
mode+=sync_flag*2 #simulated or real triggers
if verbose: mode+=8
if sync_mode==2 or mode&4:
sftp = comm.sftp
dst = '/tmp/triggerSync'
src = os.path.abspath(os.path.join(os.path.dirname(__file__), 'triggerSync'))
sftp.put(src, dst)
sftp.chmod(dst, 0o755)
cmd = 'LD_LIBRARY_PATH=/opt/ppmac/libppmac/ ' + dst
cmd+=' %g %g %d'%(pt2pt_time,timeOfs,arg)
#cmd+=' %d %g %g %g'%(mode,pt2pt_time,timeOfs,timeCor)
cmd+=' %d %g %g'%(mode,pt2pt_time+timeCor,timeOfs)
self.cmdSync = cmd
print ('starting '+cmd)
self.syncShell=comm.shell_channel(cmd)

View File

@@ -17,63 +17,36 @@ verbose bits:
0x20 plot pvt trajectory (before motion)
0x40 print sync details
Gather motor order
idx 0 1 2 3 4 5 6
OLD Motor[3].ActPos Motor[2].ActPos Motor[1].ActPos Motor[3].DesPos Motor[2].DesPos Motor[1].DesPos Gate3[1].Chan[1].UserFlag
if mode&1:
self.plot_trajectory()
if mode&2:
self.plot_pos_error()
if mode&4:
self.plot_bode(xy=(3,1),mode=31,db=True) # FX
self.plot_bode(xy=(2,0),mode=31,db=True) # FY
if mode&8:
self.plot_trigger_jitter()
Gather motor order
idx 0 1 2 3 4
NEW Motor[1].ActPos Motor[2].ActPos Motor[1].DesPos Motor[2].DesPos Gate3[1].Chan[1].UserFlag
NEW y.ActPos x.ActPos y.DesPos x.DesPos Gate3[1].Chan[1].UserFlag
OLD->NEW
0->none
1->1
2->0
3->none
4->3
5->2
Mot 1: Stage Y Parker MX80L D11 25mm one pole cycle = 13mm = 2048 phase_step
Mot 2: Stage X Parker MX80L D11 25mm one pole cycle = 13mm = 2048 phase_step
Mot 3: Rotation stage LS Mecapion MDM-DC06DNC0H 32 poles = 1 rev = 16*2048=32768 phase_step
Mot 4: Stage X Stada Stepper 670mA 200 poles 1 rev = 100*2048 phase_step (2 stepper motor)
Mot 5: Stage Z Stada Stepper 670mA 200 poles 1 rev = 100*2048 phase_step (2 stepper motor)
Enc 6: Interferometer Y
Enc 7: Interferometer X
tunnel PowerBrick port locally
------------------------------
Port 22 on PowerBrick is the ssh server port
Port 2332 on PowerBrick is the gather port of gather_server
PPMAC=SAR-CPPM-EXPMX1
rsync -vai ~/Documents/prj/SwissFEL/PBTools/pbtools/gather/gather_server root@$PPMAC:/tmp/
ssh root@$PPMAC
LD_LIBRARY_PATH=/opt/ppmac/libppmac/ /tmp/gather_server
rsync -vai ~/Documents/prj/SwissFEL/PBTools/pbtools/gather/gather_server root@$PPMAC:/tmp
ssh -L 10001:localhost:22 root@$PPMAC 'uname -a'
ssh -L 10002:localhost:2332 root@$PPMAC 'uname -a'
-> this tunnels port 22 and 2332 of $PPMAC to 10001 and 10002 of localhost
('uname -a' is just to execute a command and return, because the port remains open)
Tests:
nc localhost 10001
nc localhost 10002
list close ssh tunnel
---------------------
lsof -i -n | egrep 'ssh'
lsof -i -n | grep '127.0.0.1:1000'
s.a. https://docs.google.com/document/d/1soSuCZYyfGf_ntcgG_Y1_WeGuo_687OuFn0s4sMj1uY/
'''
from __future__ import print_function
#from __future__ import absolute_import,division,generators,nested_scopes,print_function,unicode_literals,with_statement
try: raw_input;input=raw_input
except NameError: pass
@@ -305,7 +278,7 @@ class DebugPlot:
hl = ax.plot(pts[:, 0], pts[:, 1], 'r.', label='points')
hl += ax.plot(pts[:, 0], pts[:, 1], 'y--', label='direct')
hl += ax.plot(rec[:, 3], rec[:, 2], 'b-', label='DesPos') # desired path
hl += ax.plot(rec[:, 1], rec[:, 0], 'g-', label='ActPos') # actual path
hl += ax.plot(rec[:, 1], rec[:, 0], 'g.', label='ActPos') # actual path
try:
pvt = self.pvt
except AttributeError:
@@ -333,6 +306,23 @@ class DebugPlot:
ax2.legend(loc='best')
plt.show(block=False)
def plot_dbg(self):
rec = self.rec # yA,xA,yD,xD,trig
ts=self.meta['srv_per']*self.meta['acq_per']
fig = plt.figure('debug plot')
ax = fig.add_subplot(1, 1, 1)
t=np.arange(rec.shape[0],dtype=np.uint32)
#address=("Motor[1].ActPos", "Motor[2].ActPos", "Motor[1].DesPos", "Motor[2].DesPos", "Gate3[1].Chan[1].UserFlag","EncTable[20].DeltaPos")
hl = []
rec[0, 5]=rec[0, 6]=0
hl += ax.plot(t, rec[:, 6], 'rx', label='EncTable[20].DeltaPos')
hl += ax.plot(t, rec[:, 5], 'b-', label='Coord[1].TimeBase')
hl += ax.plot(t, rec[:, 4], 'g.', label='Gate3[1].Chan[1].UserFlag')
ax.xaxis.set_label_text('ms (timebase: %g ms per data point)' % ts)
ax.legend(loc='best')
plt.show(block=False)
def plot_gather(self,mode=255):
try:
meta=self.meta
@@ -355,6 +345,10 @@ class DebugPlot:
if mode&8:
self.plot_trigger_jitter()
#self.plot_dbg()
plt.show(block=False)
def plot_bode(self,xy=(0,1),mode=25,db=True):
@@ -445,8 +439,8 @@ class DebugPlot:
plt.show(block=False)
def load_npz(self,fn='/tmp/shapepath.npz'):
fh=np.load(fn)
for k,v in fh.iteritems():
fh=np.load(fn,allow_pickle=True)
for k,v in fh.items():
setattr(self,k,v)
self.meta=self.meta.item()
@@ -571,15 +565,6 @@ class ShapePath(MotionBase):
for x in np.arange(mult)*shift:
stack.append(pts+(x,y))
pts=np.vstack(stack)
#xx,yy=np.meshgrid(range(w), range(h))
#pts=np.array([xx.reshape(-1),yy.reshape(-1)],dtype=np.float).transpose()*pitch
#if xy:
#else:
# smlpitch
#pts+=ofs
self.points=pts
@@ -654,36 +639,26 @@ class ShapePath(MotionBase):
comm=self.comm
gt=self.gather
gt.set_phasemode(False)
if self.meta['sync_flag']&2:
meta=self.meta
if meta['sync_flag']&2:
address=("Motor[1].ActPos","Motor[2].ActPos","Motor[1].DesPos","Motor[2].DesPos","Coord[1].Q[11]")
else:
address=("Motor[1].ActPos","Motor[2].ActPos","Motor[1].DesPos","Motor[2].DesPos","Gate3[1].Chan[1].UserFlag")
#address=("Motor[1].ActPos","Motor[2].ActPos","Motor[1].DesPos","Motor[2].DesPos","Gate3[1].Chan[1].UserFlag","EncTable[20].PrevEnc")
#address=("Motor[1].ActPos", "Motor[2].ActPos", "Motor[1].DesPos", "Motor[2].DesPos", "Gate3[1].Chan[1].UserFlag","Coord[1].TimeBase","EncTable[20].DeltaPos")
gtMaxLn=gt.set_address(*address)
if acq_per is None:
ovhdTime=100
acq_per=int(np.ceil((self.meta['pt2pt_time']*self.points.shape[0]+ovhdTime)/(gtMaxLn*self.meta['srv_per'])))
acq_per=int(np.ceil((meta['pt2pt_time']*self.points.shape[0]+ovhdTime)/(gtMaxLn*meta['srv_per'])))
gt.set_property(MaxSamples=1000000, Period=acq_per)
#gt.set_property(Period=acq_per)
self.meta.update({'acq_per':acq_per,'address':address})
meta.update({'acq_per':acq_per,'address':address})
def setup_coord_trf(self,fx='X',fy='Y',cz='0'):
# FY:1, FX:2, ROT_Y:3, CX:4, CZ:5,
if self.comm is None: return
comm = self.comm
gpascii = comm.gpascii
prg = '''&1a
#1-> Y
#2-> X
#3-> A
&0#4->0
&0#5->0
&0#6->0
&0#7->0
#1..3j/
//#8->0
//#1..8j/
'''
prg=f'''&1a
&1#3->0
&1#4->0
@@ -699,7 +674,6 @@ class ShapePath(MotionBase):
#5->{cz}
#1,2,5j/
'''
gpascii.send_block(prg)
def setup_motion(self,prgId=2,fnPrg=None,mode=0,**kwargs):
@@ -719,6 +693,22 @@ class ShapePath(MotionBase):
numPad : number of padding points to reduce aliasing (default=16)
'''
prg=['close all buffers','open prog %d'%(prgId)]
#Use EncTable 20 as Frame counter
#dont use Coord[1].DesTimeBase as the time base but use the virtual encoder:
# Coord[1].pDesTimeBase=Coord[1].DesTimeBase.a #(default)
prg.append('''\
EncTable[20].type = 1 //32 Bit uint
EncTable[20].pEnc = Sys.Udata[0].a
EncTable[20].index1 = 0
EncTable[20].index2 = 0
EncTable[20].index3 = 0
EncTable[20].index4 = 0
EncTable[20].index5 = 0
EncTable[20].index5 = 0
EncTable[20].ScaleFactor=1E-3
//Coord[1].pDesTimeBase=EncTable[20].DeltaPos.a
''')
verb=self.verbose
comm=self.comm
if comm is not None:
@@ -768,7 +758,8 @@ class ShapePath(MotionBase):
plt.show(block=False)
pv[1:-1, (2, 3)]*=CoordFeedTime #scaling for Deltatau
prg.append(' linear abs')
prg.append('linear abs')
#prg.append('X%g Y%g' % tuple(pv[0, (0,1)]-(120,120)))
prg.append('X%g Y%g' % tuple(pv[0, (0,1)]))
prg.append('dwell 10')
try: prg.extend(self.sync_prg.split('\n'))
@@ -781,6 +772,10 @@ class ShapePath(MotionBase):
prg.append(' pvt%g abs'%pt2pt_time) #100ms to next position
for idx in range(1,pv.shape[0]):
prg.append('X%g:%g Y%g:%g'%tuple(pv[idx,(0,2,1,3)]))
#if idx%78==10: #sync data all 256 points
# prg.append('Coord[1].Q[12]=Sys.ServoCount')
# #prg.append(f'Coord[1].Q[13]={idx}')
# prg.append(f'Coord[1].Q[13]=Motor[1].DesPos')
prg.append('X%g Y%g' % tuple(pv[-1, (0,1)]))
if cnt>1:
prg.append('dwell 10')
@@ -813,6 +808,7 @@ class ShapePath(MotionBase):
def gather_upload(self,fnRec=None):
gt=self.gather
comm=self.comm
gt.wait_stopped(verbose=True)
self.rec=rec=gt.upload()
@@ -824,23 +820,24 @@ class ShapePath(MotionBase):
del self.syncShell
pts=self.points
ofsy=-rec[0,2]+pts[0,1]
ofsx=-rec[0,3]+pts[0,0]
rec[:,(1,3)]+=ofsx
rec[:,(0,2)]+=ofsy
ofsy=comm.gpascii.get_variable("Motor[1].HomePos", type_=float)
ofsx=comm.gpascii.get_variable("Motor[2].HomePos", type_=float)
rec[:,(0,2)]-=ofsy
rec[:,(1,3)]-=ofsx
if fnRec:
np.savez_compressed(fnRec, rec=rec, pts=pts, meta=self.meta)
if __name__=='__main__':
#import logging
#logger = logging.getLogger(__name__)
#logger = logging.getLogger('pbtools.misc.pp_comm')
#logger.setLevel(logging.DEBUG)
#logging.basicConfig(format=('%(asctime)s %(name)-12s '
# '%(levelname)-8s %(message)s'),
# datefmt='%m-%d %H:%M',
# )
import logging
_log=logging.getLogger(__name__)
logging.getLogger('pbtools.misc.pp_comm').setLevel(logging.INFO)
logging.getLogger('paramiko').setLevel(logging.WARNING)
logging.getLogger('matplotlib').setLevel(logging.INFO)
#https://docs.python.org/3/library/logging.html#logging.Formatter add %(name)s to identify the logger
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ')
import argparse
def unique_filename(fnBase):
i = 0;
@@ -854,20 +851,6 @@ if __name__=='__main__':
def run_test(args):
#dp=DebugPlot();dp.plot_gather();return
#import socket
#s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # create an INET, STREAMing socket
#s.settimeout(.1)
#try:
# port=22 #ssh port
# s.connect((args.host, port)) # try to connect to port
# s.close()
#except (socket.error, socket.gaierror) as e:
# comm=gather=None
#else:
# comm = PPComm(host=args.host)
# gather = Gather(comm)
hpp=args.host.split(':')
param={'host':hpp[0]}
if len(hpp)>1:
@@ -885,7 +868,7 @@ if __name__=='__main__':
#sp = ShapePath(comm, gather, args.verbose,sync_mode=0,pt2pt_time=10)
#simulated start and frame trigger no sync
sp = ShapePath(comm, gather, args.verbose,sync_mode=1,sync_flag=3)
#sp = ShapePath(comm, gather, args.verbose,sync_mode=1,sync_flag=3)
#simulated start and frame trigger with sync
#sp = ShapePath(comm, gather, args.verbose,sync_mode=2,sync_flag=3)
@@ -894,7 +877,7 @@ if __name__=='__main__':
#sp = ShapePath(comm, gather, args.verbose,sync_mode=1,sync_flag=1)
#simulated start real frame trigger with sync
#sp = ShapePath(comm, gather, args.verbose,sync_mode=2,sync_flag=1)
sp = ShapePath(comm, gather, args.verbose,sync_mode=2,sync_flag=1)
fn='/tmp/shapepath'
#fn =unique_filename('ShapePathAnalyser/records/19_01_24/spiral')
@@ -927,18 +910,36 @@ if __name__=='__main__':
#sp.gen_spiral_points(rStart=100,rInc=130,numSeg=4,numCir=2, ofs=(0, 0))
#sp.gen_grid_points(w=20,h=20,pitch=100,rnd=0,ofs=(0,+2000));sp.sort_points(False);
#sp.gen_grid_points(w=5,h=10,pitch=100,rnd=0,ofs=(0,+2000));sp.sort_points(False,10);
sp.gen_grid_points(w=125,h=125,pitch=3,rnd=0,ofs=(0,+2000));sp.sort_points(False,125); sp.meta['pt2pt_time']=5
#sp.gen_grid_points(w=50,h=50,pitch=120,rnd=0,ofs=(0,+2000));sp.sort_points(False,50); sp.meta['pt2pt_time']=10
#sp.gen_grid_points(w=16,h=16,pitch=120,rnd=0,ofs=(0,+2000));sp.sort_points(False,16); sp.meta['pt2pt_time']=10
#12.5x12.5
sp.gen_grid_points(w=78,h=78,pitch=120,rnd=0,ofs=(-10000,-12000));sp.sort_points(False,78); sp.meta['pt2pt_time']=10
#23.0x23.0 -> only 3 data points from shot to shot: 6,9,12,14,17,20...
#sp.gen_grid_points(w=162,h=162,pitch=120,rnd=0,ofs=(-10000,-12000));sp.sort_points(False,162); sp.meta['pt2pt_time']=10
#sp.gen_grid_points(w=1,h=10,pitch=100,rnd=0,ofs=(0,0))
#sp.gen_spiral_points(rStart=100,rInc=20,numSeg=8,numCir=32, ofs=(0, 0))
#sp.gen_spiral_points(rStart=100,rInc=10,numSeg=2,numCir=32, phase=45, ofs=(0, 0))
#sp.gen_spiral_points(rStart=100,rInc=10,numSeg=4,numCir=32, ofs=(0, 0))
#sp.gen_closed_shifted()
gtMaxLn=116508;ovhdTime=100
gtMaxLn=int(sp.comm.gpascii.get_variable('Gather.MaxLines')) # 116508
if gtMaxLn==0:
sp.setup_gather(acq_per=1)
gtMaxLn=int(sp.comm.gpascii.get_variable('Gather.MaxLines')) # 116508
ovhdTime=100
acq_per=int(np.ceil((sp.meta['pt2pt_time']*sp.points.shape[0]+ovhdTime)/(gtMaxLn*sp.meta['srv_per'])))
if args.verbose&0x01:
print(f'''\
Gather.MaxLines:{gtMaxLn}
Gather.Period: {acq_per}
meta: {sp.meta}
Filename: {fn}.[prg|npz]
''')
sp.setup_gather(acq_per=acq_per)
sp.setup_sync(verbose=args.verbose&32,timeOfs=0.05)
sp.setup_sync(verbose=args.verbose&0x40,timeOfs=0.03,timeCor=0.0005)
sp.setup_coord_trf() # reset to shape path system
#sp.meta['pt2pt_time']=10 #put between setup_sync and setup_motion to have more motion points than FEL syncs
sp.setup_motion(fnPrg=fn+'.prg', mode=3, scale=1.,dwell=10)
@@ -946,8 +947,11 @@ if __name__=='__main__':
#sp.setup_motion(fnPrg=fn + '.prg', mode=1, scale=0,dwell=10)
sp.homing() #homing if needed
sp.run() #start motion program
print('wait_armed')
sp.wait_armed() # wait until motors are at first position
print('trigger')
sp.trigger(0.5) #send a start trigger (if needed) ater given time
print('progress..')
if not comm is None:
while True:
p=sp.progress()
@@ -955,55 +959,31 @@ if __name__=='__main__':
print('progress %d/%d'%(p,sp.points.shape[0]));time.sleep(.1)
sp.gather_upload(fnRec=fn+'.npz')
dp=DebugPlot(sp);dp.plot_gather(mode=11)
print('done')
plt.show(block=False)
input('press return')
#sp.plot_points(sp.points);plt.show()
#>>>run gather and plot trajectory<<<
#return
from optparse import OptionParser, IndentedHelpFormatter
class MyFormatter(IndentedHelpFormatter):
'helper class for formating the OptionParser'
def __init__(self):
IndentedHelpFormatter.__init__(self)
def format_epilog(self, epilog):
if epilog:
return epilog
else:
return ""
def parse_args():
'main command line interpreter function'
#usage: gpasciiCommunicator.py --host=PPMACZT84 myPowerBRICK.cfg
(h, t)=os.path.split(sys.argv[0]);cmd='\n '+(t if len(h)>3 else sys.argv[0])+' '
exampleCmd=('-v15',
'--host=localhost:10001:10002'
)
epilog=__doc__+'''
Examples:'''+''.join(map(lambda s:cmd+s, exampleCmd))+'\n '
fmt=MyFormatter()
parser=OptionParser(epilog=epilog, formatter=fmt)
parser.add_option('-v', '--verbose', type="int", dest='verbose', help='verbosity bits (see below)', default=0)
parser.add_option('--host', help='hostname', default='SAR-CPPM-EXPMX1')
'--host=localhost:10001:10002 -v 0x5d',
'--host=localhost:10001:10002 -v 0x59'
)
epilog=__doc__+'\nExamples:'+''.join(map(lambda s:cmd+s, exampleCmd))+'\n '
parser=argparse.ArgumentParser(epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("-m", "--mode", type=lambda x:int(x, 0), help="mode (see bitmasks) default=0x%(default)x", default=0xff)
parser.add_argument('-v', '--verbose', type=lambda x:int(x, 0), dest='verbose', help='verbosity bits (see below) default=0x%(default)x', default=0x00)
parser.add_argument('--host', help='hostname', default='SAR-CPPM-EXPMX1')
#parser.add_option('--host', help='hostname', default='localhost:10001:10002')
#parser.add_option('--host', help='hostname')
(args, other)=parser.parse_args()
args.other=other
args=parser.parse_args()
_log.info('Arguments:{}'.format(args.__dict__))
run_test(args)
#------------------ Main Code ----------------------------------
#dp=DebugPlot('/tmp/shapepath.npz');dp.plot_gather(mode=11);plt.show()
#exit(0)
#ssh_test()
ret=parse_args()
exit(ret)