From 9c7bb44de7b9a813bff0b00f05330a679f4804c9 Mon Sep 17 00:00:00 2001 From: Michael Langmayr Date: Fri, 14 Nov 2025 17:25:36 -0800 Subject: [PATCH 1/6] Start with fei pickoff raw daemon --- daemons/hsfei/pickoff_raw | 382 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100755 daemons/hsfei/pickoff_raw diff --git a/daemons/hsfei/pickoff_raw b/daemons/hsfei/pickoff_raw new file mode 100755 index 0000000..a834521 --- /dev/null +++ b/daemons/hsfei/pickoff_raw @@ -0,0 +1,382 @@ +#!/usr/bin/env python3 +""" +HSFEI Pickoff Mirror Raw Position Daemon + +This daemon provides control and monitoring of the FEI pickoff mirror +raw position using the LibbyDaemon framework. +""" + +import argparse +import logging +import sys +import os +from typing import Dict, Any + +from libby.daemon import LibbyDaemon +from hispec.util.pi import PIControllerBase + + +class PickoffRawDaemon(LibbyDaemon): + """Daemon for controlling the FEI pickoff mirror raw position.""" + + peer_id = "hsfei-pickoff-raw" + transport = "zmq" + bind = "tcp://*:5560" + address_book = {} + discovery_enabled = True + discovery_interval_s = 5.0 + + def __init__(self, ip_address='192.168.29.100', tcp_port=10001, axis='1'): + """Initialize the pickoff raw daemon. + + Args: + ip_address: IP address of the PI controller (default: 192.168.29.100) + tcp_port: TCP port for the PI controller (default: 10001) + axis: Axis identifier (default: '1') + """ + super().__init__() + + # PI controller configuration + self.ip_address = ip_address + self.tcp_port = tcp_port + self.axis = axis + self.device_key = None + + # PI controller instance + self.controller = PIControllerBase(quiet=False) + + # Daemon state + self.state = { + 'connected': False, + 'error': '', + } + + # Setup logging + self.logger = logging.getLogger(self.peer_id) + + # Define RPC services + self.services = { + # Status queries + "status.get": self._service_get_status, + "position.get": self._service_get_position, + "limits.get": self._service_get_limits, + "idn.get": self._service_get_idn, + + # Control commands + "move": self._service_move, + "home": self._service_home, + "stop": self._service_stop, + "servo.set": self._service_set_servo, + } + + # pub/sub topics + topics = {} + + def on_start(self, libby): + """Called when daemon starts - initialize hardware.""" + self.logger.info("Starting pickoff raw daemon") + + # Initialize hardware connection + if self.ip_address is None or self.tcp_port is None: + self.logger.error("No IP address or port specified for PI C-663 controller") + self.state['error'] = 'No IP address or port specified' + else: + try: + self._connect_hardware() + self.logger.info("Daemon started successfully and connected to hardware") + except Exception as e: + self.logger.error(f"Failed to connect to hardware: {e}") + self.logger.warning("Daemon will start but hardware is not available") + self.state['error'] = str(e) + self.state['connected'] = False + + # Publish initial status + libby.publish("pickoff.status", self.state) + + # ========== RPC Service Handlers ========== + + def _service_get_status(self, _p: Dict[str, Any]) -> Dict[str, Any]: + """Get complete status of the daemon (queries hardware).""" + if not self.state['connected']: + return {"ok": False, "error": "Not connected to hardware"} + + try: + position = self.controller.get_position(self.device_key, self.axis) + moving = self.controller.is_moving(self.device_key, self.axis) + servo_on = self.controller.servo_status(self.device_key, self.axis) + referenced = self.controller.is_controller_referenced(self.device_key, self.axis) + limit_min = self.controller.get_limit_min(self.device_key, self.axis) + limit_max = self.controller.get_limit_max(self.device_key, self.axis) + idn = self.controller.get_idn(self.device_key) + + status = { + 'connected': True, + 'position': position, + 'moving': moving, + 'servo_on': servo_on, + 'referenced': referenced, + 'limit_min': limit_min, + 'limit_max': limit_max, + 'idn': idn, + } + + return {"ok": True, "status": status} + except Exception as e: + self.logger.error(f"Error reading status: {e}") + self.state['error'] = str(e) + return {"ok": False, "error": str(e)} + + def _service_get_position(self, _p: Dict[str, Any]) -> Dict[str, Any]: + """Get current position from hardware.""" + if not self.state['connected']: + return {"ok": False, "error": "Not connected to hardware"} + + try: + position = self.controller.get_position(self.device_key, self.axis) + return {"ok": True, "position": position, "units": "mm"} + except Exception as e: + self.logger.error(f"Error reading position: {e}") + self.state['error'] = str(e) + return {"ok": False, "error": str(e)} + + def _service_get_limits(self, _p: Dict[str, Any]) -> Dict[str, Any]: + """Get travel limits from hardware.""" + if not self.state['connected']: + return {"ok": False, "error": "Not connected to hardware"} + + try: + limit_min = self.controller.get_limit_min(self.device_key, self.axis) + limit_max = self.controller.get_limit_max(self.device_key, self.axis) + return { + "ok": True, + "limit_min": limit_min, + "limit_max": limit_max, + "units": "mm" + } + except Exception as e: + self.logger.error(f"Error reading limits: {e}") + self.state['error'] = str(e) + return {"ok": False, "error": str(e)} + + def _service_get_idn(self, _p: Dict[str, Any]) -> Dict[str, Any]: + """Get controller identification from hardware.""" + if not self.state['connected']: + return {"ok": False, "error": "Not connected to hardware"} + + try: + idn = self.controller.get_idn(self.device_key) + return {"ok": True, "idn": idn} + except Exception as e: + self.logger.error(f"Error reading IDN: {e}") + self.state['error'] = str(e) + return {"ok": False, "error": str(e)} + + def _service_move(self, p: Dict[str, Any]) -> Dict[str, Any]: + """Move to target position. + + Args: + p: {"target": } + """ + if not self.state['connected']: + return {"ok": False, "error": "Not connected to hardware"} + + target = p.get("target") + if target is None: + return {"ok": False, "error": "Missing 'target' parameter"} + + if not isinstance(target, (int, float)): + return {"ok": False, "error": "'target' must be a number"} + + target = float(target) + self.logger.info(f"Moving to position: {target} mm") + + try: + # Command the move + self.controller.set_position(self.device_key, self.axis, target, blocking=False) + return {"ok": True, "target": target} + + except Exception as e: + self.logger.error(f"Error moving to target: {e}") + self.state['error'] = str(e) + return {"ok": False, "error": str(e)} + + def _service_home(self, _p: Dict[str, Any]) -> Dict[str, Any]: + """Home/reference the pickoff mirror.""" + if not self.state['connected']: + return {"ok": False, "error": "Not connected to hardware"} + + self.logger.info("Homing pickoff mirror (FRF)") + + try: + # Execute reference move + success = self.controller.reference_move( + self.device_key, + self.axis, + method="FRF", + blocking=False + ) + + if success: + return {"ok": True, "status": "homing"} + else: + return {"ok": False, "error": "Homing failed to start"} + + except Exception as e: + self.logger.error(f"Error during homing: {e}") + self.state['error'] = str(e) + return {"ok": False, "error": str(e)} + + def _service_stop(self, _p: Dict[str, Any]) -> Dict[str, Any]: + """Stop any ongoing motion.""" + if not self.state['connected']: + return {"ok": False, "error": "Not connected to hardware"} + + self.logger.info("Stopping motion") + + try: + # Halt all motion on the controller + self.controller.halt_motion(self.device_key) + return {"ok": True, "status": "stopped"} + + except Exception as e: + self.logger.error(f"Error stopping motion: {e}") + self.state['error'] = str(e) + return {"ok": False, "error": str(e)} + + def _service_set_servo(self, p: Dict[str, Any]) -> Dict[str, Any]: + """Enable or disable the servo. + + Args: + p: {"enable": true/false} + """ + if not self.state['connected']: + return {"ok": False, "error": "Not connected to hardware"} + + enable = p.get("enable") + if enable is None: + return {"ok": False, "error": "Missing 'enable' parameter"} + + if not isinstance(enable, bool): + return {"ok": False, "error": "'enable' must be boolean"} + + try: + self.controller.set_servo(self.device_key, self.axis, enable=enable) + self.logger.info(f"Servo {'enabled' if enable else 'disabled'}") + return {"ok": True, "servo_on": enable} + + except Exception as e: + self.logger.error(f"Error setting servo: {e}") + self.state['error'] = str(e) + return {"ok": False, "error": str(e)} + + # ========== Hardware Connection ========== + + def _connect_hardware(self): + """Connect to the PI C-663 Mercury Stepper Controller hardware.""" + self.logger.info(f"Connecting to PI C-663 at {self.ip_address}:{self.tcp_port}") + + try: + # Connect to the PI C-663 Mercury Stepper Controller via TCP + self.controller.connect_tcp(self.ip_address, self.tcp_port) + self.device_key = (self.ip_address, self.tcp_port) + self.state['connected'] = True + + # Get controller information + idn = self.controller.get_idn(self.device_key) + self.logger.info(f"Connected to: {idn}") + + # Get axis information + axes = self.controller.get_axes(self.device_key) + self.logger.info(f"Available axes: {axes}") + + # Read initial position for logging + position = self.controller.get_position(self.device_key, self.axis) + self.logger.info(f"Current position: {position} mm") + + self.logger.info("Connected to PI C-663 Mercury Stepper Controller") + + except Exception as e: + self.logger.error(f"Failed to connect to PI C-663: {e}") + self.state['connected'] = False + # Write error to state + self.state['error'] = str(e) + + def on_stop(self, _libby): + """Cleanup when daemon shuts down.""" + self.logger.info("Shutting down pickoff raw daemon") + + # Disconnect from PI C-663 controller + if self.state['connected'] and self.controller: + try: + self.controller.disconnect_all() + self.logger.info("Disconnected from PI C-663") + except Exception as e: + self.logger.error(f"Error disconnecting: {e}") + + self.state['connected'] = False + + +def main(): + """Main entry point for the daemon.""" + parser = argparse.ArgumentParser( + description='HSFEI Pickoff Mirror Raw Daemon (PI C-663 Stepper)' + ) + parser.add_argument( + '-i', '--ip', + type=str, + default='192.168.29.100', + help='IP address of the PI C-663 controller (default: 192.168.29.100)' + ) + parser.add_argument( + '--tcp-port', + type=int, + default=10001, + help='TCP port for PI controller communication (default: 10001)' + ) + parser.add_argument( + '-a', '--axis', + type=str, + default='1', + help='Axis identifier (default: 1)' + ) + + args = parser.parse_args() + + # Setup logging to file and console + log_level = logging.INFO + log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + + # Create logger + logger = logging.getLogger() + logger.setLevel(log_level) + + # Console handler + console_handler = logging.StreamHandler() + console_handler.setLevel(log_level) + console_handler.setFormatter(logging.Formatter(log_format)) + logger.addHandler(console_handler) + + # File handler + file_handler = logging.FileHandler('pickoff_raw_daemon.log') + file_handler.setLevel(log_level) + file_handler.setFormatter(logging.Formatter(log_format)) + logger.addHandler(file_handler) + + # Create and run daemon + try: + daemon = PickoffRawDaemon( + ip_address=args.ip, + tcp_port=args.tcp_port, + axis=args.axis, + ) + daemon.serve() + except KeyboardInterrupt: + print("\nDaemon interrupted by user") + sys.exit(0) + except Exception as e: + print(f"Error running daemon: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == '__main__': + main() From dc91507137e7592df4f0864936a42744b311d1d4 Mon Sep 17 00:00:00 2001 From: Michael Langmayr Date: Fri, 14 Nov 2025 17:34:40 -0800 Subject: [PATCH 2/6] cleanup --- daemons/hsfei/pickoff_raw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemons/hsfei/pickoff_raw b/daemons/hsfei/pickoff_raw index a834521..f034a15 100755 --- a/daemons/hsfei/pickoff_raw +++ b/daemons/hsfei/pickoff_raw @@ -3,7 +3,7 @@ HSFEI Pickoff Mirror Raw Position Daemon This daemon provides control and monitoring of the FEI pickoff mirror -raw position using the LibbyDaemon framework. +raw position. """ import argparse From f7cdf26269dbe00851e59fbc0e579ed46a668555 Mon Sep 17 00:00:00 2001 From: Michael Langmayr Date: Mon, 17 Nov 2025 11:14:29 -0800 Subject: [PATCH 3/6] renamed to hsfei.pickoff --- daemons/hsfei/{pickoff_raw => pickoff} | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) rename daemons/hsfei/{pickoff_raw => pickoff} (95%) diff --git a/daemons/hsfei/pickoff_raw b/daemons/hsfei/pickoff similarity index 95% rename from daemons/hsfei/pickoff_raw rename to daemons/hsfei/pickoff index f034a15..2d99707 100755 --- a/daemons/hsfei/pickoff_raw +++ b/daemons/hsfei/pickoff @@ -1,25 +1,23 @@ #!/usr/bin/env python3 """ -HSFEI Pickoff Mirror Raw Position Daemon +HSFEI Pickoff MirrorDaemon -This daemon provides control and monitoring of the FEI pickoff mirror -raw position. +This daemon provides control and monitoring of the FEI pickoff mirrorusing Libby. """ import argparse import logging import sys -import os from typing import Dict, Any from libby.daemon import LibbyDaemon from hispec.util.pi import PIControllerBase -class PickoffRawDaemon(LibbyDaemon): - """Daemon for controlling the FEI pickoff mirror raw position.""" +class HsfeiPickoffDaemon(LibbyDaemon): + """Daemon for controlling the FEI pickoff mirror position.""" - peer_id = "hsfei-pickoff-raw" + peer_id = "hsfei.pickoff" transport = "zmq" bind = "tcp://*:5560" address_book = {} @@ -27,7 +25,7 @@ class PickoffRawDaemon(LibbyDaemon): discovery_interval_s = 5.0 def __init__(self, ip_address='192.168.29.100', tcp_port=10001, axis='1'): - """Initialize the pickoff raw daemon. + """Initialize the pickoff daemon. Args: ip_address: IP address of the PI controller (default: 192.168.29.100) @@ -74,7 +72,7 @@ class PickoffRawDaemon(LibbyDaemon): def on_start(self, libby): """Called when daemon starts - initialize hardware.""" - self.logger.info("Starting pickoff raw daemon") + self.logger.info("Starting pickoff daemon") # Initialize hardware connection if self.ip_address is None or self.tcp_port is None: @@ -303,7 +301,7 @@ class PickoffRawDaemon(LibbyDaemon): def on_stop(self, _libby): """Cleanup when daemon shuts down.""" - self.logger.info("Shutting down pickoff raw daemon") + self.logger.info("Shutting down pickoff daemon") # Disconnect from PI C-663 controller if self.state['connected'] and self.controller: @@ -319,7 +317,7 @@ class PickoffRawDaemon(LibbyDaemon): def main(): """Main entry point for the daemon.""" parser = argparse.ArgumentParser( - description='HSFEI Pickoff Mirror Raw Daemon (PI C-663 Stepper)' + description='HSFEI Pickoff Mirror Daemon (PI C-663 Stepper)' ) parser.add_argument( '-i', '--ip', @@ -357,14 +355,14 @@ def main(): logger.addHandler(console_handler) # File handler - file_handler = logging.FileHandler('pickoff_raw_daemon.log') + file_handler = logging.FileHandler('hsfei_pickoff_daemon.log') file_handler.setLevel(log_level) file_handler.setFormatter(logging.Formatter(log_format)) logger.addHandler(file_handler) # Create and run daemon try: - daemon = PickoffRawDaemon( + daemon = HsfeiPickoffDaemon( ip_address=args.ip, tcp_port=args.tcp_port, axis=args.axis, From a6629c2235a824bf592073ac8d38b04aa9c4ddc9 Mon Sep 17 00:00:00 2001 From: Michael Langmayr Date: Tue, 18 Nov 2025 18:07:49 -0800 Subject: [PATCH 4/6] made changes after testing with stage --- daemons/hsfei/pickoff | 60 ++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/daemons/hsfei/pickoff b/daemons/hsfei/pickoff index 2d99707..3ec38d3 100755 --- a/daemons/hsfei/pickoff +++ b/daemons/hsfei/pickoff @@ -18,12 +18,14 @@ class HsfeiPickoffDaemon(LibbyDaemon): """Daemon for controlling the FEI pickoff mirror position.""" peer_id = "hsfei.pickoff" - transport = "zmq" - bind = "tcp://*:5560" - address_book = {} - discovery_enabled = True + transport = "rabbitmq" + rabbitmq_url = "amqp://localhost" # RabbitMQ on hispec + discovery_enabled = False discovery_interval_s = 5.0 + # pub/sub topics + topics = {} + def __init__(self, ip_address='192.168.29.100', tcp_port=10001, axis='1'): """Initialize the pickoff daemon. @@ -32,8 +34,6 @@ class HsfeiPickoffDaemon(LibbyDaemon): tcp_port: TCP port for the PI controller (default: 10001) axis: Axis identifier (default: '1') """ - super().__init__() - # PI controller configuration self.ip_address = ip_address self.tcp_port = tcp_port @@ -52,11 +52,19 @@ class HsfeiPickoffDaemon(LibbyDaemon): # Setup logging self.logger = logging.getLogger(self.peer_id) - # Define RPC services - self.services = { + # Call parent __init__ first + super().__init__() + + def on_start(self, libby): + """Called when daemon starts - initialize hardware.""" + self.logger.info("Starting pickoff daemon") + + # Register RPC services using add_services() + services = { # Status queries "status.get": self._service_get_status, "position.get": self._service_get_position, + "target.get": self._service_get_target, "limits.get": self._service_get_limits, "idn.get": self._service_get_idn, @@ -67,12 +75,8 @@ class HsfeiPickoffDaemon(LibbyDaemon): "servo.set": self._service_set_servo, } - # pub/sub topics - topics = {} - - def on_start(self, libby): - """Called when daemon starts - initialize hardware.""" - self.logger.info("Starting pickoff daemon") + self.add_services(services) + self.logger.info(f"Registered {len(services)} RPC services using add_services()") # Initialize hardware connection if self.ip_address is None or self.tcp_port is None: @@ -107,9 +111,19 @@ class HsfeiPickoffDaemon(LibbyDaemon): limit_max = self.controller.get_limit_max(self.device_key, self.axis) idn = self.controller.get_idn(self.device_key) + # Get target position + target = None + try: + device = self.controller.devices[self.device_key] + target = device.qMOV(self.axis)[self.axis] + except Exception: + # If qMOV fails, target is unknown + pass + status = { 'connected': True, 'position': position, + 'target': target, 'moving': moving, 'servo_on': servo_on, 'referenced': referenced, @@ -137,6 +151,20 @@ class HsfeiPickoffDaemon(LibbyDaemon): self.state['error'] = str(e) return {"ok": False, "error": str(e)} + def _service_get_target(self, _p: Dict[str, Any]) -> Dict[str, Any]: + """Get target position from hardware.""" + if not self.state['connected']: + return {"ok": False, "error": "Not connected to hardware"} + + try: + device = self.controller.devices[self.device_key] + target = device.qMOV(self.axis)[self.axis] + return {"ok": True, "target": target, "units": "mm"} + except Exception as e: + self.logger.error(f"Error reading target: {e}") + self.state['error'] = str(e) + return {"ok": False, "error": str(e)} + def _service_get_limits(self, _p: Dict[str, Any]) -> Dict[str, Any]: """Get travel limits from hardware.""" if not self.state['connected']: @@ -276,7 +304,9 @@ class HsfeiPickoffDaemon(LibbyDaemon): try: # Connect to the PI C-663 Mercury Stepper Controller via TCP self.controller.connect_tcp(self.ip_address, self.tcp_port) - self.device_key = (self.ip_address, self.tcp_port) + # PI controller stores device with 3-tuple key: (ip, port, device_id) + # For single non-daisy-chain connection, device_id is always 1 + self.device_key = (self.ip_address, self.tcp_port, 1) self.state['connected'] = True # Get controller information From 3cebb399fc2fa52bc02c0c3f8a5ec86c25c61244 Mon Sep 17 00:00:00 2001 From: Michael Langmayr Date: Mon, 24 Nov 2025 12:35:46 -0800 Subject: [PATCH 5/6] add service function for connect and disconnect, fix pylint warnings --- daemons/hsfei/pickoff | 81 ++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 24 deletions(-) diff --git a/daemons/hsfei/pickoff b/daemons/hsfei/pickoff index 3ec38d3..7a5c42f 100755 --- a/daemons/hsfei/pickoff +++ b/daemons/hsfei/pickoff @@ -59,7 +59,7 @@ class HsfeiPickoffDaemon(LibbyDaemon): """Called when daemon starts - initialize hardware.""" self.logger.info("Starting pickoff daemon") - # Register RPC services using add_services() + # Register RPC services services = { # Status queries "status.get": self._service_get_status, @@ -73,10 +73,14 @@ class HsfeiPickoffDaemon(LibbyDaemon): "home": self._service_home, "stop": self._service_stop, "servo.set": self._service_set_servo, + + # Connection management + "connect": self._service_connect, + "disconnect": self._service_disconnect, } self.add_services(services) - self.logger.info(f"Registered {len(services)} RPC services using add_services()") + self.logger.info("Registered %d RPC services", len(services)) # Initialize hardware connection if self.ip_address is None or self.tcp_port is None: @@ -87,7 +91,7 @@ class HsfeiPickoffDaemon(LibbyDaemon): self._connect_hardware() self.logger.info("Daemon started successfully and connected to hardware") except Exception as e: - self.logger.error(f"Failed to connect to hardware: {e}") + self.logger.error("Failed to connect to hardware: %s", e) self.logger.warning("Daemon will start but hardware is not available") self.state['error'] = str(e) self.state['connected'] = False @@ -97,7 +101,7 @@ class HsfeiPickoffDaemon(LibbyDaemon): # ========== RPC Service Handlers ========== - def _service_get_status(self, _p: Dict[str, Any]) -> Dict[str, Any]: + def _service_get_status(self, _: Dict[str, Any]) -> Dict[str, Any]: """Get complete status of the daemon (queries hardware).""" if not self.state['connected']: return {"ok": False, "error": "Not connected to hardware"} @@ -134,11 +138,11 @@ class HsfeiPickoffDaemon(LibbyDaemon): return {"ok": True, "status": status} except Exception as e: - self.logger.error(f"Error reading status: {e}") + self.logger.error("Error reading status: %s", e) self.state['error'] = str(e) return {"ok": False, "error": str(e)} - def _service_get_position(self, _p: Dict[str, Any]) -> Dict[str, Any]: + def _service_get_position(self, _: Dict[str, Any]) -> Dict[str, Any]: """Get current position from hardware.""" if not self.state['connected']: return {"ok": False, "error": "Not connected to hardware"} @@ -147,11 +151,11 @@ class HsfeiPickoffDaemon(LibbyDaemon): position = self.controller.get_position(self.device_key, self.axis) return {"ok": True, "position": position, "units": "mm"} except Exception as e: - self.logger.error(f"Error reading position: {e}") + self.logger.error("Error reading position: %s", e) self.state['error'] = str(e) return {"ok": False, "error": str(e)} - def _service_get_target(self, _p: Dict[str, Any]) -> Dict[str, Any]: + def _service_get_target(self, _: Dict[str, Any]) -> Dict[str, Any]: """Get target position from hardware.""" if not self.state['connected']: return {"ok": False, "error": "Not connected to hardware"} @@ -161,11 +165,11 @@ class HsfeiPickoffDaemon(LibbyDaemon): target = device.qMOV(self.axis)[self.axis] return {"ok": True, "target": target, "units": "mm"} except Exception as e: - self.logger.error(f"Error reading target: {e}") + self.logger.error("Error reading target: %s", e) self.state['error'] = str(e) return {"ok": False, "error": str(e)} - def _service_get_limits(self, _p: Dict[str, Any]) -> Dict[str, Any]: + def _service_get_limits(self, _: Dict[str, Any]) -> Dict[str, Any]: """Get travel limits from hardware.""" if not self.state['connected']: return {"ok": False, "error": "Not connected to hardware"} @@ -180,11 +184,11 @@ class HsfeiPickoffDaemon(LibbyDaemon): "units": "mm" } except Exception as e: - self.logger.error(f"Error reading limits: {e}") + self.logger.error("Error reading limits: %s", e) self.state['error'] = str(e) return {"ok": False, "error": str(e)} - def _service_get_idn(self, _p: Dict[str, Any]) -> Dict[str, Any]: + def _service_get_idn(self, _: Dict[str, Any]) -> Dict[str, Any]: """Get controller identification from hardware.""" if not self.state['connected']: return {"ok": False, "error": "Not connected to hardware"} @@ -193,7 +197,7 @@ class HsfeiPickoffDaemon(LibbyDaemon): idn = self.controller.get_idn(self.device_key) return {"ok": True, "idn": idn} except Exception as e: - self.logger.error(f"Error reading IDN: {e}") + self.logger.error("Error reading IDN: %s", e) self.state['error'] = str(e) return {"ok": False, "error": str(e)} @@ -214,7 +218,7 @@ class HsfeiPickoffDaemon(LibbyDaemon): return {"ok": False, "error": "'target' must be a number"} target = float(target) - self.logger.info(f"Moving to position: {target} mm") + self.logger.info("Moving to position: %f mm", target) try: # Command the move @@ -222,11 +226,11 @@ class HsfeiPickoffDaemon(LibbyDaemon): return {"ok": True, "target": target} except Exception as e: - self.logger.error(f"Error moving to target: {e}") + self.logger.error("Error moving to target: %s", e) self.state['error'] = str(e) return {"ok": False, "error": str(e)} - def _service_home(self, _p: Dict[str, Any]) -> Dict[str, Any]: + def _service_home(self, _: Dict[str, Any]) -> Dict[str, Any]: """Home/reference the pickoff mirror.""" if not self.state['connected']: return {"ok": False, "error": "Not connected to hardware"} @@ -248,7 +252,7 @@ class HsfeiPickoffDaemon(LibbyDaemon): return {"ok": False, "error": "Homing failed to start"} except Exception as e: - self.logger.error(f"Error during homing: {e}") + self.logger.error("Error during homing: %s", e) self.state['error'] = str(e) return {"ok": False, "error": str(e)} @@ -265,7 +269,7 @@ class HsfeiPickoffDaemon(LibbyDaemon): return {"ok": True, "status": "stopped"} except Exception as e: - self.logger.error(f"Error stopping motion: {e}") + self.logger.error("Error stopping motion: %s", e) self.state['error'] = str(e) return {"ok": False, "error": str(e)} @@ -291,15 +295,44 @@ class HsfeiPickoffDaemon(LibbyDaemon): return {"ok": True, "servo_on": enable} except Exception as e: - self.logger.error(f"Error setting servo: {e}") + self.logger.error("Error setting servo: %s", e) self.state['error'] = str(e) return {"ok": False, "error": str(e)} + def _service_connect(self, _: Dict[str, Any]) -> Dict[str, Any]: + """Connect to the hardware.""" + if self.state['connected']: + return {"ok": True, "message": "Already connected"} + + if self.ip_address is None or self.tcp_port is None: + return {"ok": False, "error": "No IP address or port specified"} + + try: + self._connect_hardware() + return {"ok": True, "message": "Connected to hardware"} + except Exception as e: + self.logger.error("Failed to connect to hardware: %s", e) + return {"ok": False, "error": str(e)} + + def _service_disconnect(self, _: Dict[str, Any]) -> Dict[str, Any]: + """Disconnect from the hardware.""" + if not self.state['connected']: + return {"ok": True, "message": "Already disconnected"} + + try: + self.controller.disconnect_all() + self.state['connected'] = False + self.logger.info("Disconnected from hardware") + return {"ok": True, "message": "Disconnected from hardware"} + except Exception as e: + self.logger.error("Error disconnecting: %s", e) + return {"ok": False, "error": str(e)} + # ========== Hardware Connection ========== def _connect_hardware(self): """Connect to the PI C-663 Mercury Stepper Controller hardware.""" - self.logger.info(f"Connecting to PI C-663 at {self.ip_address}:{self.tcp_port}") + self.logger.info("Connecting to PI C-663 at %s:%s", self.ip_address, self.tcp_port) try: # Connect to the PI C-663 Mercury Stepper Controller via TCP @@ -315,16 +348,16 @@ class HsfeiPickoffDaemon(LibbyDaemon): # Get axis information axes = self.controller.get_axes(self.device_key) - self.logger.info(f"Available axes: {axes}") + self.logger.info("Available axes: %d", axes) # Read initial position for logging position = self.controller.get_position(self.device_key, self.axis) - self.logger.info(f"Current position: {position} mm") + self.logger.info("Current position: %f mm", position) self.logger.info("Connected to PI C-663 Mercury Stepper Controller") except Exception as e: - self.logger.error(f"Failed to connect to PI C-663: {e}") + self.logger.error("Failed to connect to PI C-663: %s",e) self.state['connected'] = False # Write error to state self.state['error'] = str(e) @@ -339,7 +372,7 @@ class HsfeiPickoffDaemon(LibbyDaemon): self.controller.disconnect_all() self.logger.info("Disconnected from PI C-663") except Exception as e: - self.logger.error(f"Error disconnecting: {e}") + self.logger.error("Error disconnecting: %s", e) self.state['connected'] = False From 4483ed5018999266da5f8a5754f9d12a322c2087 Mon Sep 17 00:00:00 2001 From: Michael Langmayr Date: Mon, 1 Dec 2025 16:51:08 -0800 Subject: [PATCH 6/6] change move to set_position --- daemons/hsfei/pickoff | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/daemons/hsfei/pickoff b/daemons/hsfei/pickoff index 7a5c42f..ed01974 100755 --- a/daemons/hsfei/pickoff +++ b/daemons/hsfei/pickoff @@ -69,7 +69,7 @@ class HsfeiPickoffDaemon(LibbyDaemon): "idn.get": self._service_get_idn, # Control commands - "move": self._service_move, + "position.set": self._service_set_position, "home": self._service_home, "stop": self._service_stop, "servo.set": self._service_set_servo, @@ -201,32 +201,31 @@ class HsfeiPickoffDaemon(LibbyDaemon): self.state['error'] = str(e) return {"ok": False, "error": str(e)} - def _service_move(self, p: Dict[str, Any]) -> Dict[str, Any]: - """Move to target position. + def _service_set_position(self, p: Dict[str, Any]) -> Dict[str, Any]: + """Set/move to target position. Args: - p: {"target": } + p: {"position": } """ if not self.state['connected']: return {"ok": False, "error": "Not connected to hardware"} - target = p.get("target") - if target is None: - return {"ok": False, "error": "Missing 'target' parameter"} + position = p.get("position") + if position is None: + return {"ok": False, "error": "Missing 'position' parameter"} - if not isinstance(target, (int, float)): - return {"ok": False, "error": "'target' must be a number"} + if not isinstance(position, (int, float)): + return {"ok": False, "error": "'position' must be a number"} - target = float(target) - self.logger.info("Moving to position: %f mm", target) + position = float(position) + self.logger.info("Setting position to: %f mm", position) try: - # Command the move - self.controller.set_position(self.device_key, self.axis, target, blocking=False) - return {"ok": True, "target": target} + self.controller.set_position(self.device_key, self.axis, position, blocking=False) + return {"ok": True, "position": position, "units": "mm"} except Exception as e: - self.logger.error("Error moving to target: %s", e) + self.logger.error("Error setting position: %s", e) self.state['error'] = str(e) return {"ok": False, "error": str(e)}