diff --git a/tomcat_bec/devices/aerotech/AerotechAutomation1.py b/tomcat_bec/devices/aerotech/AerotechAutomation1.py index a295bd4..93f56a0 100644 --- a/tomcat_bec/devices/aerotech/AerotechAutomation1.py +++ b/tomcat_bec/devices/aerotech/AerotechAutomation1.py @@ -709,15 +709,22 @@ class aa1AxisPsoDistance(aa1AxisPsoBase): # ######################################################################## # Bluesky flyer interface - def prepare(self) -> DeviceStatus: + def prepare(self, distance=None) -> DeviceStatus: """ Arm trigger for a synchronous or asynchronous acquisition""" - if self._distance_value is not None: - # Rearm the configured array - if isinstance(self._distance_value, (np.ndarray, list, tuple)): - self.dstArrayRearm.set(1).wait() - # Start monitoring the counters - self.dstEventsEna.set("On").wait() - self.dstCounterEna.set("On").wait() + if distance is not None: + # Write a new array + if isinstance(distance, (float, int)): + self.dstDistance.set(distance).wait() + elif isinstance(distance, (np.ndarray, list, tuple)): + self.dstDistanceArr.set(distance).wait() + else: + if self._distance_value is not None: + # Rearm the already configured array + if isinstance(self._distance_value, (np.ndarray, list, tuple)): + self.dstArrayRearm.set(1).wait() + # Start monitoring the counters + self.dstEventsEna.set("On").wait() + self.dstCounterEna.set("On").wait() status = DeviceStatus(self) status.set_finished() return status diff --git a/tomcat_bec/devices/aerotech/AerotechSimpleSequenceTemplate.ascript b/tomcat_bec/devices/aerotech/AerotechSimpleSequenceTemplate.ascript index 73a3d97..ad56e0f 100644 --- a/tomcat_bec/devices/aerotech/AerotechSimpleSequenceTemplate.ascript +++ b/tomcat_bec/devices/aerotech/AerotechSimpleSequenceTemplate.ascript @@ -73,17 +73,24 @@ program PsoDistanceEventsOff($axis) PsoWindowConfigureEvents($axis, PsoWindowEventMode.None) PsoWaveformOff($axis) - var $iPsoArrayPosAddr as integer = PSO_ADDR // Simple PSO trigger pattern - var $iPsoArrayPos[] as real = [{% for psoDist in scan.psoBoundsPos[:-1] %} UnitsToCounts($axis, {{ psoDist }}), {% endfor %} UnitsToCounts($axis, {{ scan.psoBoundsPos[-1] }}) ] - var $iPsoArrayNeg[] as real = [{% for psoDist in scan.psoBoundsNeg[:-1] %} UnitsToCounts($axis, {{ psoDist }}), {% endfor %} UnitsToCounts($axis, {{ scan.psoBoundsNeg[-1] }}) ] + var $iPsoArrayPosAddr as integer = PSO_ADDR var $iPsoArrayPosSize as integer = {{ scan.psoBoundsPosSize }} - var $iPsoArrayNegSize as integer = {{ scan.psoBoundsNegSize }} - // Calculate negative array offset (in bytes) - var $iPsoArrayNegAddr as integer = ($iPsoArrayPosAddr + length($iPsoArrayPos)) * 4 - + var $iPsoArrayPos[] as real = [{% for psoDist in scan.psoBoundsPos[:-1] %} UnitsToCounts($axis, {{ psoDist }}), {% endfor %} UnitsToCounts($axis, {{ scan.psoBoundsPos[-1] }}) ] DriveArrayWrite($axis, $iPsoArrayPos, $iPsoArrayPosAddr, length($iPsoArrayPos), DriveArrayType.PsoDistanceEventDistances) + + {% if scan.psoBoundsNeg|length > 0 %} + var $iPsoArrayNegAddr as integer = ($iPsoArrayPosAddr + length($iPsoArrayPos)) * 4 + var $iPsoArrayNegSize as integer = {{ scan.psoBoundsNegSize }} + var $iPsoArrayNeg[] as real = [{% for psoDist in scan.psoBoundsNeg[:-1] %} UnitsToCounts($axis, {{ psoDist }}), {% endfor %} UnitsToCounts($axis, {{ scan.psoBoundsNeg[-1] }}) ] DriveArrayWrite($axis, $iPsoArrayNeg, $iPsoArrayNegAddr, length($iPsoArrayNeg), DriveArrayType.PsoDistanceEventDistances) + {% else %} + var $iPsoArrayNegAddr as integer = ($iPsoArrayPosAddr + length($iPsoArrayPos)) * 4 + var $iPsoArrayNegSize as integer = 0 + var $iPsoArrayNeg[] as real = [] + // DriveArrayWrite($axis, $iPsoArrayNeg, $iPsoArrayNegAddr, length($iPsoArrayNeg), DriveArrayType.PsoDistanceEventDistances) + {% endif %} + PsoDistanceConfigureArrayDistances($axis, $iPsoArrayPosAddr, length($iPsoArrayPos), 0) PsoDistanceCounterOn($axis) @@ -113,33 +120,15 @@ program /////////////////////////////////////////////////////////// // Start the actual scanning /////////////////////////////////////////////////////////// - for $ii = 0 to ($iNumRepeat-1) - // Feedback on progress - $rglobal[4] = $ii - if $eScanType == ScanType.POS - PsoDistanceConfigureArrayDistances($axis, $iPsoArrayPosAddr, $iPsoArrayPosSize, 0) - MoveAbsolute($axis, $fPosEnd, $fVelScan) - WaitForInPosition($axis) - MoveAbsolute($axis, $fPosStart, $fVelScan) - WaitForInPosition($axis) - elseif $eScanType == ScanType.NEG - PsoDistanceConfigureArrayDistances($axis, $iPsoArrayPosAddr, $iPsoArrayPosSize, 0) - MoveAbsolute($axis, $fPosEnd, $fVelScan) - WaitForInPosition($axis) - MoveAbsolute($axis, $fPosStart, $fVelScan) - WaitForInPosition($axis) - elseif $eScanType == ScanType.POSNEG - if ($ii % 2) == 0 - PsoDistanceConfigureArrayDistances($axis, $iPsoArrayPosAddr, $iPsoArrayPosSize, 0) - MoveAbsolute($axis, $fPosEnd, $fVelScan) - WaitForInPosition($axis) - elseif ($ii % 2) == 1 - PsoDistanceConfigureArrayDistances($axis, $iPsoArrayNegAddr, $iPsoArrayNegSize, 0) - MoveAbsolute($axis, $fPosStart, $fVelScan) - WaitForInPosition($axis) - end - elseif $eScanType == ScanType.NEGPOS + if $eScanType == ScanType.POS || $eScanType == ScanType.NEG + PsoDistanceConfigureArrayDistances($axis, $iPsoArrayPosAddr, $iPsoArrayPosSize, 0) + MoveAbsolute($axis, $fPosEnd, $fVelScan) + WaitForInPosition($axis) + elseif $eScanType == ScanType.POSNEG || $eScanType == ScanType.NEGPOS + for $ii = 0 to ($iNumRepeat-1) + // Feedback on progress + $rglobal[4] = $ii if ($ii % 2) == 0 PsoDistanceConfigureArrayDistances($axis, $iPsoArrayPosAddr, $iPsoArrayPosSize, 0) MoveAbsolute($axis, $fPosEnd, $fVelScan) @@ -149,13 +138,16 @@ program MoveAbsolute($axis, $fPosStart, $fVelScan) WaitForInPosition($axis) end + Dwell(0.2) end - Dwell(0.2) end // Directly after scan PsoDistanceCounterOff($axis) DriveDataCaptureOff($axis, 0) DriveDataCaptureOff($axis, 1) + // move back to start position + MoveAbsolute($axis, $fPosStart, $fVelJog) + WaitForInPosition($axis) end diff --git a/tomcat_bec/scans/tomcat_scanbase.py b/tomcat_bec/scans/tomcat_scanbase.py index 7506950..f604377 100644 --- a/tomcat_bec/scans/tomcat_scanbase.py +++ b/tomcat_bec/scans/tomcat_scanbase.py @@ -211,7 +211,7 @@ class SimpleFlyer(AsyncFlyScanBase): def unstage(self): """ Wait for DAQ before unstaging""" - time.sleep(5) + time.sleep(1) yield from super().unstage() @@ -311,7 +311,7 @@ class GigaStepScanBase(ScanBase): def unstage(self): """ Wait for DAQ before unstaging""" - time.sleep(5) + time.sleep(1) yield from super().unstage() @@ -396,7 +396,7 @@ class SnapAndStepScanBase(TemplatedScanBase): def unstage(self): """ Wait for DAQ before unstaging""" - time.sleep(5) + time.sleep(1) yield from super().unstage() @@ -433,7 +433,7 @@ class SequenceScanBase(TemplatedScanBase): self.gate_high = gate_high self.gate_low = gate_low self.scan_repnum = repeats - self.scan_repmode = repmode + self.scan_repmode = repmode.upper() self.exp_time = exp_time self.exp_frames = exp_frames @@ -501,24 +501,17 @@ class SequenceScanBase(TemplatedScanBase): def prepare_positions(self): # Fill PSO vectors according to scan direction + # NOTE: Distance counter is bidirectional self.psoBoundsPos = [] self.psoBoundsNeg = [] - if self.scan_repmode in ("pos", "Pos"): + if self.scan_repmode in ("pos", "Pos", "POS", "neg", "Neg", "NEG"): self.psoBoundsPos.append(self.scan_accdistance) for ii in range(self.scan_repnum): self.psoBoundsPos += [self.gate_high, self.gate_low] - # self.psoBoundsPos[-1] = 10* (self.scan_accdistance+self.gate_low) - if self.scan_repmode in ("neg", "Neg"): - self.psoBoundsNeg.append(self.scan_accdistance) - for ii in range(self.scan_repnum): - self.psoBoundsNeg += [self.gate_high, self.gate_low] - # self.psoBoundsNeg[-1] = 10* (self.scan_accdistance+self.gate_low) - if self.scan_repmode in ("posneg", "PosNeg"): + self.psoBoundsPos = self.psoBoundsPos[:-1] + if self.scan_repmode in ("posneg", "PosNeg", "POSNEG", "negpos", "NegPos", "NEGPOS"): self.psoBoundsPos = [self.scan_accdistance, self.gate_high] self.psoBoundsNeg = [self.scan_accdistance + self.gate_low, self.gate_high] - if self.scan_repmode in ("negpos", "NegPos"): - self.psoBoundsNeg = [self.scan_accdistance, self.gate_high] - self.psoBoundsPos = [self.scan_accdistance + self.gate_low, self.gate_high] self.filesubs['psoBoundsPos'] = self.psoBoundsPos self.filesubs['psoBoundsNeg'] = self.psoBoundsNeg @@ -529,7 +522,7 @@ class SequenceScanBase(TemplatedScanBase): def unstage(self): """ Wait for DAQ before unstaging""" - time.sleep(5) + time.sleep(1) yield from super().unstage() diff --git a/tomcat_bec/scripts/demoscans.py b/tomcat_bec/scripts/demoscans.py index 8db467a..f22f954 100644 --- a/tomcat_bec/scripts/demoscans.py +++ b/tomcat_bec/scripts/demoscans.py @@ -68,4 +68,134 @@ def demosequencescan(scan_start, gate_high, gate_low, repeats=1, repmode="PosNeg raise RuntimeError("Beamline is not in ready state") print("Handing over to 'scans.line_scan'") - scans.sequencescan(scan_start, gate_high, gate_low, exp_time=exp_time, exp_frames=exp_frames, repeats=repeats, repmode=repmode) \ No newline at end of file + scans.sequencescan(scan_start, gate_high, gate_low, exp_time=exp_time, exp_frames=exp_frames, repeats=repeats, repmode=repmode) + + +def becsequencescan(scan_start, gate_high, gate_low, repeats=1, repmode="PosNeg", + exp_time=0.005, exp_frames=180, sync='pso'): + """ Demo sequence scan with GigaFrost + + This is a small BEC user-space sequence scan at the microXAS testbench + triggering a customized scripted scan on the controller. The scan uses + a pre-written custom low-level sequence scan, so it is really minimal. + + NOTE: It calls the AeroScript template version. + + Example: + -------- + >>> demosequencescan(33, 180, 180, exp_time=0.005, exp_frames=1800, repeats=10) + """ + if not bl_check_beam(): + raise RuntimeError("Beamline is not in ready state") + + # Parameter validation before scan + if exp_frames <=0: + raise RuntimeError(f"Requires at least one frame per cycle, got {self.exp_frames}") + + # Synthetic values + scan_roix = 2016 + scan_roiy = 2016 + scan_repnum = repeats + scan_repmode = repmode.upper() + scan_velocity = gate_high / (exp_time * exp_frames) + scan_acceleration = 500 + scan_safedistance = 10 + scan_accdistance = scan_safedistance + 0.5* scan_velocity * scan_velocity / scan_acceleration + scan_ntotal = exp_frames * repeats + + if scan_repmode in ("POS", "NEG"): + scan_range = repeats *(gate_high + gate_low) + if scan_repmode=="POS": + scan_end = scan_start + scan_range + scan_accdistance + if scan_repmode=="NEG": + scan_end = scan_start - scan_range - scan_accdistance + elif scan_repmode in ("POSNEG", "NEGPOS"): + scan_range = gate_high + gate_low + if scan_repmode=="POSNEG": + scan_end = scan_start + scan_range + scan_accdistance + if scan_repmode=="NEGPOS": + scan_end = scan_start - scan_range - scan_accdistance + else: + raise RuntimeError(f"Unsupported repetition mode: {repmode}") + + + # Aerotech DDC settings: Internal event trigger: PsoEvent = 1 + t_modes = {'pso': 0, 'event': 1, 'inp0': 2, 'inp1': 4} + p_modes = {'pso': 'PsoOutput', 'event': 'PsoEvent', 'inp0': 'HighSpeedInput0RisingEdge', 'inp1': 'HighSpeedInput1RisingEdge'} + daq_trigger = t_modes[sync] + pso_trigger = p_modes[sync] + + # Drice data collection config + daqname = "es1_ddaq" + daqcfg = {'ntotal': scan_ntotal, 'trigger': daq_trigger} + # Gigafrost config + camname = "gfclient" + camcfg = { + "ntotal": scan_ntotal, + "nimages": exp_frames, + "exposure": 1000 * exp_time, + "period": 2000*exp_time, + "pixel_width": scan_roix, + "pixel_height": scan_roiy + } + + # Fill PSO vectors according to scan direction + # NOTE: Distance counter is bidirectional + psoBoundsPos = [] + psoBoundsNeg = [] + if scan_repmode in ("POS", "NEG"): + psoBoundsPos.append(scan_accdistance) + for ii in range(scan_repnum): + psoBoundsPos += [gate_high, gate_low] + if scan_repmode in ("POSNEG", "NEGPOS"): + psoBoundsPos = [scan_accdistance, gate_high] + psoBoundsNeg = [scan_accdistance + gate_low, gate_high] + + # PSO config + psocfg = { + "distance": psoBoundsPos, + "wmode": "toggle" + } + + # Move to start position + dev.es1_roty.move(scan_start).wait() + + # Configure all devices + dev.gfclient.configure(camcfg) + dev.es1_ddaq.configure(daqcfg) + dev.es1_psod.configure(psocfg) + + # Stage all devices + dev.gfclient.stage() + dev.es1_ddaq.stage() + dev.es1_psod.stage() + dev.daq_stream1.stage() + + # Kick off devices + dev.gfclient.kickoff() + dev.es1_ddaq.kickoff() + dev.es1_psod.kickoff() + print("Handing over to 'scans.line_scan'") + + if scan_repmode in ["POS", "NEG"]: + dev.es1_psod.prepare(psoBoundsPos) + dev.es1_roty.move(scan_end).wait() + elif scan_repmode in ["POSNEG", "NEGPOS"]: + for ii in range(scan_repnum): + if ii%2==0: + dev.es1_psod.prepare(psoBoundsPos) + dev.es1_roty.move(scan_end).wait() + if ii%2==1: + dev.es1_psod.prepare(psoBoundsNeg) + dev.es1_roty.move(scan_start).wait() + + # Stage all devices + dev.gfclient.unstage() + dev.es1_ddaq.unstage() + dev.es1_psod.unstage() + dev.daq_stream1.unstage() + + # Move back to start + dev.es1_roty.move(scan_start).wait() + +