diff --git a/superxas_bec/devices/timepix/timepix.py b/superxas_bec/devices/timepix/timepix.py index 8190d98..d2f027d 100644 --- a/superxas_bec/devices/timepix/timepix.py +++ b/superxas_bec/devices/timepix/timepix.py @@ -30,7 +30,7 @@ from superxas_bec.devices.timepix.timepix_fly_client.timepix_fly_interface impor ) -class AndStatus(_AndStatus): +class AndStatus(StatusBase): """Custom AndStatus for TimePix detector.""" def __init__(self, left: StatusBase | DeviceStatus, right: StatusBase | DeviceStatus, **kwargs): @@ -54,8 +54,12 @@ class AndStatus(_AndStatus): # At least one is done. # If it failed, do not wait for the second one. if (not l_success) and l_done: + if self._externally_initiated_completion is True: + return self.set_exception(self.left.exception()) elif (not r_success) and r_done: + if self._externally_initiated_completion is True: + return self.set_exception(self.right.exception()) elif l_success and r_success and l_done and r_done: @@ -67,6 +71,25 @@ class AndStatus(_AndStatus): self.left.add_callback(inner) self.right.add_callback(inner) + def __repr__(self): + return "({self.left!r} & {self.right!r})".format(self=self) + + def __str__(self): + return ( + "{0}(done={1.done}, " + "success={1.success})" + "".format(self.__class__.__name__, self) + ) + + def __contains__(self, status: StatusBase) -> bool: + for child in [self.left, self.right]: + if child == status: + return True + if isinstance(child, AndStatus): + if status in child: + return True + + return False logger = bec_logger.logger @@ -214,7 +237,7 @@ class Timepix(PSIDeviceBase, TimePixControl): self._troistep = 1 self._troin = 5000 self._pv_timeout = 3 - self._readout_time = 2e-3 # 2ms readout time + self._readout_time = 2.1e-3 # 2.1ms readout time to ensure readout is >2ms, required from ASI serval server.. self.r_lock = threading.RLock() # Lock to access the message buffer safely super().__init__( name=name, prefix=prefix, scan_info=scan_info, device_manager=device_manager, **kwargs @@ -404,7 +427,7 @@ class Timepix(PSIDeviceBase, TimePixControl): other_config = OtherConfigModel( TRoiStep=self.troistep, TRoiN=self.troin, - output_uri=f"tcp://{self.backend.hostname}:{self.backend.socket_port}", + output_uri=f"tcp:{self.backend.hostname}:{self.backend.socket_port}", ) logger.info(f"Current TimePixFly configuration: {other_config}") pixel_map = self.pixel_map @@ -412,6 +435,7 @@ class Timepix(PSIDeviceBase, TimePixControl): # Fetch the backend socket info net_add = self.backend.timepix_fly_client.get_net_addresses() + logger.info(f"Using net_add for timepix_fly backend {net_add}") self.cam.raw_file_template.set("").wait(timeout=self._pv_timeout) self.cam.raw_file_path.set(f"tcp://connect@{net_add.address}").wait( timeout=self._pv_timeout @@ -433,36 +457,30 @@ class Timepix(PSIDeviceBase, TimePixControl): """Called when the device is triggered.""" def trigger_callback(status: DeviceStatus): - """Trigger callback to start the acquisition.""" + """Trigger callback to start the acquisition.""" if status.done: + logger.info(f"Calling acquire on detector.") status.device.cam.acquire.put(1) + logger.info(f"Status callback from backend trigger. done {status.done}, success {status.success} and exception {status._exception}") # Detector will be ready to start, as either pre_scan or the status_camera from a previous # trigger will ensure that the detector is in ACQUIRESTATUS.DONE state. status_backend = DeviceStatus(self) - # Add callback that starts the acquisition on the detector - status_backend.add_callback(trigger_callback) # Prepare the camera status that resolves when the camera is finished acquiring status_camera = TransitionStatus( - self.cam.acquire_busy, [ACQUIRESTATUS.ACQUIRING, ACQUIRESTATUS.DONE] + self.cam.acquire_busy, [ACQUIRESTATUS.DONE, ACQUIRESTATUS.ACQUIRING, ACQUIRESTATUS.DONE] ) # Prepare the backend, attach the status to the state of the backend status_backend = self.backend.on_trigger(status=status_backend) - # TODO cleanup and test - def failed_to_start_collect_cb(status: DeviceStatus): - """Callback to handle failure to start the collect.""" - if not status.done: - logger.error("Failed to start collect on Timepix Fly backend.") - status.device.backend.timepix_fly_client.stop_collect() - status_collect_backend = DeviceStatus(self, timeout=10) - status_collect_backend.add_callback(failed_to_start_collect_cb) self.backend.timepix_fly_client.add_status_callback( status=status_collect_backend, success=[TimePixFlyStatus.COLLECT], - error=[TimePixFlyStatus.EXCEPT, TimePixFlyStatus.SHUTDOWN], + error=[TimePixFlyStatus.EXCEPT, TimePixFlyStatus.SHUTDOWN, TimePixFlyStatus.CONFIG], ) + # Add callback that starts the acquisition on the detector + status_backend.add_callback(trigger_callback) status = AndStatus(status_backend, status_camera) st = AndStatus(status, status_collect_backend) @@ -513,11 +531,11 @@ if __name__ == "__main__": # pragma: no cover timepix.wait_for_connection(all_signals=True, timeout=10) timepix.on_connected() print("Timepix connected and initialized.") - for exp_time, frames_per_trigger in zip([10, 1], [1, 5]): + for exp_time, frames_per_trigger, runs in zip([0.1,1], [20,5], [10,5]): time.sleep(0.5) print( f"Sleeping for 0.5 seconds before starting the scan with exp_time={exp_time} " - f"and frames_per_trigger={frames_per_trigger}." + f"and frames_per_trigger={frames_per_trigger}. and runs {runs}" ) timepix.scan_info.msg.scan_parameters.update( @@ -534,36 +552,24 @@ if __name__ == "__main__": # pragma: no cover msgs = [] # for ii in range(runs): print(f"Starting scan...; exp_time={exp_time}, frames_per_trigger={frames_per_trigger}") - status = timepix.trigger() - print("Timepix trigger sent.") - start_time = time.time() - while not status.done: - try: - status.wait(timeout=1) - except Exception as exc: - print(f" Trigger status not done yet after ({time.time() - start_time:.2f}s)") - if time.time() - start_time > 30: - print("Breaking loop manually after 30 seconds of waiting.") - break - - # if timepix.xes_data.get() is not None: - # # msgs.append(timepix.backend.msg_buffer) - # print( - # f"Events in energy rois {timepix.xes_data.get().signals[timepix.xes_data.name]['value'].sum()}" - # ) - # events = timepix.xes_info.get().signals[ - # f"{timepix.xes_info.name}_tds_total_events" - # ]["value"] - # print(f"Total number of events: {events}") + for run in range(runs): + print(f"Starting run {run} for exp_time {exp_time}.") + status = timepix.trigger() + start_time = time.time() + while not status.done: + try: + status.wait(timeout=1) + except Exception as exc: + print(f" Trigger status not done yet after ({time.time() - start_time:.2f}s)") + if time.time() - start_time > 20: + print("Breaking loop manually after 20 seconds of waiting.") + break + n_messages = len(timepix._msg_dump) + logger.warning(f"Messages in Buffer is {len(timepix._msg_dump)}")#, with types: {[';'.join([var.get('type') for var in timepix._msg_dump])]}") status = timepix.complete() print("Waiting for timepix to complete.") status.wait(timeout=10) print("Timepix scan completed.") - n_messages = len(timepix._msg_dump) - logger.warning( - f"Received new messages: Length of Buffer is {n_messages}, last message {timepix._msg_dump.get(n_messages-1, 'N/A') if n_messages>0 else 'N/A'}" - ) - timepix.unstage() print("Timepix unstaged.") except Exception as e: diff --git a/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_backend.py b/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_backend.py index 144639d..80f1c8f 100644 --- a/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_backend.py +++ b/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_backend.py @@ -9,6 +9,7 @@ hooks for all the relevant ophyd interface, 'on_stage', from __future__ import annotations +import time import atexit import json import signal @@ -122,9 +123,9 @@ class TimepixFlyBackend: success=[TimePixFlyStatus.CONFIG], error=[TimePixFlyStatus.EXCEPT, TimePixFlyStatus.SHUTDOWN], ) - if other_config.output_uri != f"tcp:{self.hostname}:{self.socket_port}": - other_config.output_uri = f"tcp:{self.hostname}:{self.socket_port}" - logger.info(f"Setting output URI to {other_config.output_uri}.") + # if other_config.output_uri != f"tcp:{self.hostname}:{self.socket_port}": + # other_config.output_uri = f"tcp:{self.hostname}:{self.socket_port}" + # logger.info(f"Setting output URI to {other_config.output_uri}.") # Make sure backend is in config state try: status.wait(timeout=5.0) @@ -137,6 +138,14 @@ class TimepixFlyBackend: raise TimeoutError( "Timepix Fly backend state did not reach config state. Most likely a timeout error. Please check log for detailed error message." ) + status = StatusBase() + + self.timepix_fly_client.add_status_callback( + status, + success=[TimePixFlyStatus.CONFIG], + error=[TimePixFlyStatus.EXCEPT, TimePixFlyStatus.SHUTDOWN], + ) + logger.info(f"Setting other config, backend {other_config}") self.timepix_fly_client.set_other_config(other_config) self.timepix_fly_client.set_pixel_map(pixel_map) @@ -156,6 +165,8 @@ class TimepixFlyBackend: Returns: StatusBase | DeviceStatus: The status object that will be updated with the operation's result """ + # TODO add check that backend is in CONFIG! + time.sleep(0.05) if status is None: status = StatusBase() self.timepix_fly_client.add_status_callback( diff --git a/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_client.py b/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_client.py index 435c1aa..dcc7d3f 100644 --- a/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_client.py +++ b/superxas_bec/devices/timepix/timepix_fly_client/timepix_fly_client.py @@ -344,6 +344,7 @@ class TimepixFlyClient: Start the TimePix detector by sending a GET request to the start endpoint. This method is a wrapper around the REST API call to start the detector. """ + logger.info(f"Start send from pixfly client") self._get(get_cmd="?start=true") self._started = True @@ -478,6 +479,7 @@ class TimepixFlyClient: raise ValueError( f"Value must be an instance of OtherConfigModel. Received {type(other_config)}, {other_config}." ) + # logger.info(f"Value send via rest from set_other_config {other_config.model_dump()}") self._put(put_cmd="other-config", value=other_config.model_dump(), put_response_model=None) def get_net_addresses(self) -> NetAddresses: