diff --git a/superxas_bec/devices/timepix/test_script.py b/superxas_bec/devices/timepix/test_script.py index db81409..eb63587 100644 --- a/superxas_bec/devices/timepix/test_script.py +++ b/superxas_bec/devices/timepix/test_script.py @@ -46,11 +46,12 @@ if __name__ == """__main__""": # # mock_server = TimePixFlyMockServer() # server = TimepixServer(name='server', prefix='X10DA-ES-TPX1:cam1:') # Create a Timepix object - rest_url = "p4-0017.psi.ch:8452" - ws_url = "p4-0017:8452/ws" - data_server_host = "p4-0017.psi.ch" + rest_url = "p4-0017.psi.ch:8453" + ws_url = "p4-0017:8453/ws" + # data_server_host = "x10da-bec-001.psi.ch" + # port = 3030 timepix = Timepix( - name="TimePixDetector", rest_url=rest_url, ws_url=ws_url, data_server_host=data_server_host + name="TimePixDetector", prefix='X10DA-ES-TPX1:', rest_url=rest_url, ws_url=ws_url ) timepix.on_connected() timepix.wait_for_connection(all_signals=True, timeout=10) diff --git a/superxas_bec/devices/timepix/timepix.py b/superxas_bec/devices/timepix/timepix.py index f2a7d90..080dde2 100644 --- a/superxas_bec/devices/timepix/timepix.py +++ b/superxas_bec/devices/timepix/timepix.py @@ -10,6 +10,7 @@ import socket import threading import time from typing import Literal +import atexit from bec_lib.logger import bec_logger from ophyd import ADBase @@ -114,12 +115,15 @@ class Timepix(PSIDeviceBase, TimePixControl): DATA_SERVER_PORT = 3015 # Default data server port for TimePix detector rest_url = 'p4-0017.psi.ch:8452' ws_url = 'p4-0017:8452/ws' + + Prefix of EPICS Control is 'X10DA-ES-TPX1:' """ def __init__( self, *, name, + prefix:str, rest_url: str = "localhost:8452", ws_url: str = "localhost:8452/ws", scan_info=None, @@ -142,14 +146,17 @@ class Timepix(PSIDeviceBase, TimePixControl): data_server_port (int, optional): Port for the data server. Defaults to DATA_SERVER_PORT. **kwargs: Additional keyword arguments for the PSIDeviceBase class. """ - super().__init__(name=name, scan_info=scan_info, device_manager=device_manager, **kwargs) + super().__init__(name=name, prefix=prefix, scan_info=scan_info, device_manager=device_manager, **kwargs) self._ws_url = ws_url self._rest_url = rest_url self.timepix_fly_client = TimepixFlyClient(rest_url=rest_url, ws_url=ws_url, parent=self) - if data_server_host is None or data_server_host == "localhost": + if data_server_host is None: data_server_host = ( - socket.gethostname() - ) # TODO check if that works -> Wihtout DATA_SERVER_HOST + socket.getfqdn() + ) + if not data_server_host.endswith('.psi.ch'): + logger.info(f"Found host without psi.ch domain {data_server_host}") + if data_server_port is None: data_server_port = 3015 self._data_server_host = data_server_host @@ -161,7 +168,6 @@ class Timepix(PSIDeviceBase, TimePixControl): self._data_server_started = threading.Event() # Socket server self._socket_server = None - self._socket_server_allowed_connections = 1 # How many ? self._socket_server_timeout = 0.1 self._socket_server_buffer_size = 4096 # Data buffers @@ -171,6 +177,8 @@ class Timepix(PSIDeviceBase, TimePixControl): self._decoder = json.JSONDecoder() # Wait timeout self._pv_timeout = 5.0 # Default timeout for PV operations + # Register cleanup + atexit.register(self.on_destroy) ### Beamline specifi methods for the TimePix Detector integration ### @@ -235,6 +243,7 @@ class Timepix(PSIDeviceBase, TimePixControl): TRoiStep=1, TRoiN=5000, ) + logger.info(config) # Parse pixel map from scan info if needed, otherwise use some default pixel map. pixel_map = PixelMap( chips=[ @@ -337,7 +346,7 @@ class Timepix(PSIDeviceBase, TimePixControl): def _stop_data_receiver(self): if self._data_server_thread is not None and self._data_server_thread.is_alive(): self._data_server_thread_event.set() - self._data_server_thread.join(timeout=5.0) + self._data_server_thread.join(timeout=1.0) if self._data_server_thread is not None and self._data_server_thread.is_alive(): logger.warning( f"Data server thread did not stop gracefully for device {self.name}." @@ -354,6 +363,7 @@ class Timepix(PSIDeviceBase, TimePixControl): while not self._data_server_thread_event.is_set(): try: self._data_server_started.set() + # logger.info(self._socket_server) conn, addr = self._socket_server.accept() logger.info(f"Accepted connection from {addr}") with conn: @@ -408,18 +418,13 @@ class Timepix(PSIDeviceBase, TimePixControl): with self._rlock: if self._socket_server: try: - self._data_server_thread_event.set() # Signal the data server thread to stop + self._stop_data_receiver() + except Exception as e : + logger.info(f"Failed to stop data receiver for device {self.name} with exception {e}.") + try: self._socket_server.close() except Exception as e: # pylint: disable=broad-except - logger.warning(f"Failed to shutdown socket server gracefully. Error: {e}") - self._stop_data_receiver() - self._socket_server = None - self._data_server_thread.join(timeout=5.0) - if self._data_server_thread.is_alive(): - logger.warning( - f"Data server thread for {self.name} did not stop gracefully. Forcing shutdown." - ) - self._data_server_thread = None + logger.warning(f"Failed to shutdown socket server. Error: {e}") def restart_data_receiver(self): """Restart the data receiver thread.""" @@ -440,24 +445,25 @@ class Timepix(PSIDeviceBase, TimePixControl): # AF_INET6 for IPv6, use AF_INET for IPv4; for localhost this may be different depending on the system # TODO add an os check if self._data_server_host is localhost # self._socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #BEAMLINE USAGE! - addr_info = socket.getaddrinfo( - self._data_server_host, self._data_server_port, socket.AF_UNSPEC, socket.SOCK_STREAM - ) - # Use the first valid address returned by getaddrinfo - af, socktype, proto, _, _ = addr_info[0] - self._socket_server = socket.socket(af, socktype, proto) + # addr_info = socket.getaddrinfo( + # self._data_server_host, self._data_server_port, socket.AF_UNSPEC, socket.SOCK_STREAM + # ) + # # Use the first valid address returned by getaddrinfo + # af, socktype, proto, _, _ = addr_info[0] + # self._socket_server = socket.socket(af, socktype, proto) + info = socket.getaddrinfo(self._data_server_host, port=self._data_server_port, family=socket.AF_INET, type=socket.SOCK_STREAM) + if len(info) == 0: + raise RuntimeError(f"No socket info found") + if len(info) > 1: + logger.warning(f"Found multiple socket interfaces {info}, using the first one") + af, socktype, proto, _, host_port_info = info[0] + self._socket_server = socket.socket(af, socktype, proto) self._socket_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - if af == socket.AF_INET6: - try: - self._socket_server.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) - except (AttributeError, OSError): - # This option might not exist on some systems, so we handle the exception - pass - self._socket_server.bind((self._data_server_host, self._data_server_port)) - self._socket_server.listen(self._socket_server_allowed_connections) + self._socket_server.bind(host_port_info) + self._socket_server.listen(1) # Only allow one connection self._socket_server.settimeout(self._socket_server_timeout) self._start_data_receiver() - logger.info(f"Data server started on {self._data_server_host}:{self._data_server_port}") + logger.info(f"Data server started on {host_port_info}") # pylint: disable=protected-access @@ -469,7 +475,7 @@ if __name__ == "__main__": mock_server = TimePixFlyMockServer() # Create a Timepix object - timepix = Timepix(name="TimePixDetector") + timepix = Timepix(name="TimePixDetector", prefix='') timepix.on_connected() timepix.stage() timepix.pre_scan()