From 096c7c04c0b26635669c102c82217d74df52564e Mon Sep 17 00:00:00 2001 From: Yashvi-Sharma Date: Wed, 19 Nov 2025 16:16:28 -0800 Subject: [PATCH 1/3] Initial commit, daemon for blue cal box etalon attenuator --- daemons/hscal/yjetatten.py | 124 +++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 daemons/hscal/yjetatten.py diff --git a/daemons/hscal/yjetatten.py b/daemons/hscal/yjetatten.py new file mode 100644 index 0000000..f321597 --- /dev/null +++ b/daemons/hscal/yjetatten.py @@ -0,0 +1,124 @@ +from libby import Libby +from libby.daemon import LibbyDaemon +import configparser +from hispec.util.ozoptics.dd100mc import OZController + +class YJetAtten(LibbyDaemon): + """ + Daemon to control the YJ attenuator via OZ Optics DD100-MC controller. + """ + peer_id = "yjetatten" + bind = "tcp://*:5555" + address_book = { + #"yjetatten": "tcp://192.168.29.:5555", + "peer-A": "tcp://127.0.0.1:5556" + } + dev = None + host = None + port = None + discovery_enabled = True + discovery_interval_s = 2.0 + + def on_start(self, libby: Libby) -> None: + # set up the daemon from config and initialize the device + config = configparser.ConfigParser() + config.read('hscal.config') + self.host = config.get("Device Control","yjetatten_host") + self.port = int(config.get("Device Control","yjetatten_port")) + self.dev = OZController() + self.add_services({ + "connect": lambda p: self.connect(), + "disconnect": lambda p: self.disconnect(), + "initialize": lambda p: self.initialize(), + "status": lambda p: self.status(), + "get.attenuation": lambda p: self.get_atomic_value(item='atten'), + "set.attenuation": lambda p: self.set_attenuation(p.get('value')), + "get.position": lambda p: self.get_atomic_value(item='pos'), + "set.position": lambda p: self.set_position(p.get('position')), + }) + + ret = self.connect() + if ret.get("Connect") == "Failed": + libby.publish("yjetatten", {"Daemon Startup": "Failed", "Error": ret.get("Error")}) + ret = self.initialize() + if ret.get("Initialize") == "Failed": + libby.publish("yjetatten", {"Daemon Startup": "Failed", "Error": ret.get("Error")}) + libby.publish("yjetatten", {"Daemon Startup": "Success"}) + + + def on_stop(self, libby: Libby | None) -> None: + # clean up on daemon stop + ret = self.disconnect() + if ret.get("Disconnect") == "Failed": + libby.publish("yjetatten", {"Daemon Shutdown": "Failed", "Error": ret.get("Error")}) + else: + libby.publish("yjetatten", {"Daemon Shutdown": "Success"}) + + + def connect(self): + """ handles connection to the device """ + args = [self.host, self.port] + try: + self.dev.connect(*args, con_type='tcp') + except Exception as e: + return {"Connect": f"Failed", "Error": str(e)} + if not self.dev.is_connected(): + return {"Connect": "Failed", "Error": "Unknown"} + return {"Connect": "Success"} + + def disconnect(self): + """ handles disconnection from the device """ + try: + self.dev.disconnect() + except Exception as e: + return {"Disconnect": f"Failed", "Error": str(e)} + if self.dev.is_connected(): + return {"Disconnect": "Failed", "Error": "Unknown"} + return {"Disconnect": "Success"} + + def initialize(self): + """ initializes the device (i.e. homing) """ + ret = self.dev.initialize_controller() + if not self.dev.homed: + return {"Initialize": "Failed", "Error": ret["error"] if "error" in ret else "Unknown"} + return {"Initialize": "Success"} + + def status(self): + """ gets the device params """ + ret = self.dev.get_params() + return {"Status": ret} + + def get_atomic_value(self, item: str): + """ gets an atomic value from the device """ + if item not in ['atten','pos']: + return {"Value": None, "Error": "Invalid item requested"} + ret = self.dev.get_atomic_value(item) + if ret is None: + return {"Value": None, "Error": "Failed to get value"} + else: + return {"Value": ret} + + def set_attenuation(self, value: float): + """ sets the attenuation value on the device """ + ret = self.dev.set_attenuation(value) + if 'error' in ret: + return {"Set Attenuation": "Failed", "Error": ret["error"]} + return {"Set Attenuation": "Success", "Current Attenuation": ret['data']} + + def set_position(self, position: int): + """ sets the position value on the device """ + ret = self.dev.set_position(position) + if 'error' in ret: + return {"Set Position": "Failed", "Error": ret["error"]} + return {"Set Position": "Success", "Current Position": ret['data']} + +if __name__ == "__main__": + yj_attenuator = YJetAtten() + yj_attenuator.serve() + + + + + + + From 0f40d8a69b8f76d309aae5749238c3b3f85cd8f8 Mon Sep 17 00:00:00 2001 From: Yashvi-Sharma Date: Thu, 4 Dec 2025 14:46:53 -0800 Subject: [PATCH 2/3] updated after changes to ozoptics low level module --- daemons/hscal/yjetatten.py | 212 ++++++++++++++++++++++++++----------- 1 file changed, 148 insertions(+), 64 deletions(-) diff --git a/daemons/hscal/yjetatten.py b/daemons/hscal/yjetatten.py index f321597..3c424c1 100644 --- a/daemons/hscal/yjetatten.py +++ b/daemons/hscal/yjetatten.py @@ -1,6 +1,5 @@ -from libby import Libby +import logging from libby.daemon import LibbyDaemon -import configparser from hispec.util.ozoptics.dd100mc import OZController class YJetAtten(LibbyDaemon): @@ -8,109 +7,194 @@ class YJetAtten(LibbyDaemon): Daemon to control the YJ attenuator via OZ Optics DD100-MC controller. """ peer_id = "yjetatten" - bind = "tcp://*:5555" - address_book = { - #"yjetatten": "tcp://192.168.29.:5555", - "peer-A": "tcp://127.0.0.1:5556" - } - dev = None - host = None - port = None + group_id = "hscal" + + # RabbitMQ config + transport = "rabbitmq" + rabbitmq_url = "amqp://localhost" discovery_enabled = True discovery_interval_s = 2.0 - def on_start(self, libby: Libby) -> None: - # set up the daemon from config and initialize the device - config = configparser.ConfigParser() - config.read('hscal.config') - self.host = config.get("Device Control","yjetatten_host") - self.port = int(config.get("Device Control","yjetatten_port")) + # pub/sub topics + topics = {} + + def __init__(self, host=None, port=None): + """ initialize the YJ attenuator daemon + Args: + host (str): hostname or IP address of the DD100-MC controller + port (int): port number of the DD100-MC controller + """ + self.host = host + self.port = port + + # OZoptics DD100-MC controller instance self.dev = OZController() - self.add_services({ + + # daemon state + self.state = { + 'connected': False, + 'error': '' + } + + # set up logging + self.logger = logging.getLogger(self.peer_id) + + # call parent init + super().__init__() + + + def on_start(self, libby): + """ called when daemon starts """ + self.logger.info("Starting YJ attenuator daemon") + + services = { "connect": lambda p: self.connect(), "disconnect": lambda p: self.disconnect(), "initialize": lambda p: self.initialize(), - "status": lambda p: self.status(), - "get.attenuation": lambda p: self.get_atomic_value(item='atten'), - "set.attenuation": lambda p: self.set_attenuation(p.get('value')), - "get.position": lambda p: self.get_atomic_value(item='pos'), - "set.position": lambda p: self.set_position(p.get('position')), - }) - - ret = self.connect() - if ret.get("Connect") == "Failed": - libby.publish("yjetatten", {"Daemon Startup": "Failed", "Error": ret.get("Error")}) - ret = self.initialize() - if ret.get("Initialize") == "Failed": - libby.publish("yjetatten", {"Daemon Startup": "Failed", "Error": ret.get("Error")}) - libby.publish("yjetatten", {"Daemon Startup": "Success"}) - - - def on_stop(self, libby: Libby | None) -> None: + "reset": lambda p: self.reset(), + "params.get": lambda p: self.get_stage_params(), + "attenuation.get": lambda p: self.get_atomic_value(item='atten'), + "attenuation.set": lambda p: self.set_attenuation(p.get('attenuation')), + "position.get": lambda p: self.get_atomic_value(item='pos'), + "position.set": lambda p: self.set_position(p.get('position')), + "position.step": lambda p: self.step(p.get('direction')), + } + self.add_services(services) + self.logger.info('Registered %d RPC services', len(services)) + + if self.host is None or self.port is None: + self.logger.error("No host or port specified") + self.state['error'] = "No host or port specified" + else: + ret = self.connect() + if not ret["ok"]: + self.state['error'] = ret.get("error") + else: + ret = self.initialize() + if not ret["ok"]: + self.state['error'] = ret.get("error") + else: + self.state['connected'] = True + self.state['error'] = '' + + libby.publish("yjetatten.status", self.state) + + def on_stop(self, libby): # clean up on daemon stop ret = self.disconnect() - if ret.get("Disconnect") == "Failed": - libby.publish("yjetatten", {"Daemon Shutdown": "Failed", "Error": ret.get("Error")}) + if not ret["ok"]: + libby.publish("yjetatten", {"Daemon Shutdown": "Failed", "Error": ret.get("error")}) else: libby.publish("yjetatten", {"Daemon Shutdown": "Success"}) def connect(self): """ handles connection to the device """ - args = [self.host, self.port] + if self.state['connected']: + return {"ok": True, "message": "Already connected"} + try: - self.dev.connect(*args, con_type='tcp') + self.dev.connect(host=self.host, port=self.port, con_type='tcp') + if self.dev.is_connected(): + self.logger.info("Connected to YJ Attenuator controller at %s:%d", self.host, self.port) + return {"ok": True, "message": "Connected to controller"} + else: + self.logger.error("Failed to connect to YJ Attenuator controller") + return {"ok": False, "error": "Failed to connect to controller"} except Exception as e: - return {"Connect": f"Failed", "Error": str(e)} - if not self.dev.is_connected(): - return {"Connect": "Failed", "Error": "Unknown"} - return {"Connect": "Success"} + self.logger.error("Exception while connecting to YJ Attenuator controller: %s", str(e)) + return {"ok": False, "error": f"Failed to connect to controller {str(e)}"} def disconnect(self): """ handles disconnection from the device """ + if not self.state['connected']: + return {"ok": True, "message": "Already disconnected"} + try: self.dev.disconnect() + if not self.dev.is_connected(): + self.logger.info("Disconnected from YJ Attenuator controller") + return {"ok": True, "message": "Disconnected from controller"} + else: + self.logger.error("Failed to disconnect from YJ Attenuator controller") + return {"ok": False, "error": "Failed to disconnect from controller"} except Exception as e: - return {"Disconnect": f"Failed", "Error": str(e)} - if self.dev.is_connected(): - return {"Disconnect": "Failed", "Error": "Unknown"} - return {"Disconnect": "Success"} + self.logger.error("Exception while disconnecting from YJ Attenuator controller: %s", str(e)) + return {"ok": False, "error": f"Failed to disconnect from controller {str(e)}"} + def initialize(self): """ initializes the device (i.e. homing) """ - ret = self.dev.initialize_controller() - if not self.dev.homed: - return {"Initialize": "Failed", "Error": ret["error"] if "error" in ret else "Unknown"} - return {"Initialize": "Success"} + if self.dev.initialize(): + self.logger.info("Initialized YJ Attenuator controller") + return {"ok": True, "message": "Successfully homed device"} + else: + self.logger.error("Failed to initialize YJ Attenuator controller") + return {"ok": False, "error": "Failed to home device"} + + def reset(self): + """ resets the device """ + ret = self.dev.reset() + if ret is not None: + self.logger.info("Reset YJ Attenuator controller") + return {"ok": True, "message": "Successfully reset device", "reset": ret} + else: + self.logger.error("Failed to reset YJ Attenuator controller") + return {"ok": False, "error": "Failed to reset device"} - def status(self): + def get_stage_params(self): """ gets the device params """ ret = self.dev.get_params() - return {"Status": ret} + if ret is not None: + self.logger.debug("get_params: %s", str(ret)) + return {"ok": True, "params": ret} + else: + self.logger.error("Failed to get YJ Attenuator controller params") + return {"ok": False, "error": "Failed to get device params"} def get_atomic_value(self, item: str): """ gets an atomic value from the device """ if item not in ['atten','pos']: - return {"Value": None, "Error": "Invalid item requested"} + self.logger.error("Invalid item requested: %s", item) + return {"ok": False, "error": "Invalid item requested"} ret = self.dev.get_atomic_value(item) if ret is None: - return {"Value": None, "Error": "Failed to get value"} + self.logger.error(f"Failed to get {item} from YJ Attenuator controller") + return {"ok": False, "error": f"Failed to get {item} value"} else: - return {"Value": ret} + self.logger.debug("get_%s: %s", item, str(ret)) + return {"ok": True, item: ret} - def set_attenuation(self, value: float): + def set_attenuation(self, attenuation: float): """ sets the attenuation value on the device """ - ret = self.dev.set_attenuation(value) - if 'error' in ret: - return {"Set Attenuation": "Failed", "Error": ret["error"]} - return {"Set Attenuation": "Success", "Current Attenuation": ret['data']} + if self.dev.set_attenuation(attenuation): + self.logger.info("Successfully set attenuation to %f", attenuation) + return {"ok": True, "message": "Successfully set attenuation"} + else: + self.logger.error("Failed to set attenuation to %f", attenuation) + return {"ok": False, "error": "Failed to set attenuation"} def set_position(self, position: int): """ sets the position value on the device """ - ret = self.dev.set_position(position) - if 'error' in ret: - return {"Set Position": "Failed", "Error": ret["error"]} - return {"Set Position": "Success", "Current Position": ret['data']} + if self.dev.set_pos(position): + self.logger.info("Successfully set position to %d", position) + return {"ok": True, "message": "Successfully set position"} + else: + self.logger.error("Failed to set position to %d", position) + return {"ok": False, "error": "Failed to set position"} + + def step(self, direction: str): + """ steps the device in the given direction """ + if direction not in ['F', 'B']: + return {"ok": False, "error": "Invalid direction, must be 'F' or 'B'"} + ret = self.dev.step(direction) + if ret is not None: + self.logger.info("Successfully stepped device %s to position %d", direction, ret) + return {"ok": True, "message": "Successfully stepped device", "position": ret} + else: + self.logger.error("Failed to step device %s", direction) + return {"ok": False, "error": "Failed to step device"} + if __name__ == "__main__": yj_attenuator = YJetAtten() From b8fa4685afe137adb4c2d6c1f0ca8b33a7ae4dee Mon Sep 17 00:00:00 2001 From: Yashvi-Sharma Date: Tue, 23 Dec 2025 17:11:58 -0800 Subject: [PATCH 3/3] updated with HispecDaemon base class --- daemons/hscal/yjetatten.py | 104 +++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 56 deletions(-) diff --git a/daemons/hscal/yjetatten.py b/daemons/hscal/yjetatten.py index 3c424c1..fc603b7 100644 --- a/daemons/hscal/yjetatten.py +++ b/daemons/hscal/yjetatten.py @@ -1,8 +1,7 @@ -import logging -from libby.daemon import LibbyDaemon +from hispec import HispecDaemon from hispec.util.ozoptics.dd100mc import OZController -class YJetAtten(LibbyDaemon): +class YJetAtten(HispecDaemon): """ Daemon to control the YJ attenuator via OZ Optics DD100-MC controller. """ @@ -12,23 +11,16 @@ class YJetAtten(LibbyDaemon): # RabbitMQ config transport = "rabbitmq" rabbitmq_url = "amqp://localhost" - discovery_enabled = True - discovery_interval_s = 2.0 + discovery_enabled = False - # pub/sub topics - topics = {} + def __init__(self): + super().__init__() - def __init__(self, host=None, port=None): - """ initialize the YJ attenuator daemon - Args: - host (str): hostname or IP address of the DD100-MC controller - port (int): port number of the DD100-MC controller - """ - self.host = host - self.port = port + self.ip_address = self.get_config("hardware.ip_address", default=None) + self.tcp_port = self.get_config("hardware.tcp_port", default=None) # OZoptics DD100-MC controller instance - self.dev = OZController() + self.controller = OZController() # daemon state self.state = { @@ -36,12 +28,6 @@ def __init__(self, host=None, port=None): 'error': '' } - # set up logging - self.logger = logging.getLogger(self.peer_id) - - # call parent init - super().__init__() - def on_start(self, libby): """ called when daemon starts """ @@ -62,9 +48,9 @@ def on_start(self, libby): self.add_services(services) self.logger.info('Registered %d RPC services', len(services)) - if self.host is None or self.port is None: - self.logger.error("No host or port specified") - self.state['error'] = "No host or port specified" + if self.ip_address is None or self.tcp_port is None: + self.logger.error("Missing IP address or TCP port for YJ Etalon DD100-MC controller") + self.state['error'] = "No ip address or tcp port specified" else: ret = self.connect() if not ret["ok"]: @@ -76,6 +62,7 @@ def on_start(self, libby): else: self.state['connected'] = True self.state['error'] = '' + self.logger.info("YJ attenuator daemon connected and initialized successfully") libby.publish("yjetatten.status", self.state) @@ -83,58 +70,63 @@ def on_stop(self, libby): # clean up on daemon stop ret = self.disconnect() if not ret["ok"]: - libby.publish("yjetatten", {"Daemon Shutdown": "Failed", "Error": ret.get("error")}) + self.state['error'] = ret.get("error") else: - libby.publish("yjetatten", {"Daemon Shutdown": "Success"}) + self.state['connected'] = False + self.state['error'] = '' + self.logger.info("YJ attenuator daemon stopped successfully") def connect(self): """ handles connection to the device """ - if self.state['connected']: + if self.controller.is_connected(): return {"ok": True, "message": "Already connected"} try: - self.dev.connect(host=self.host, port=self.port, con_type='tcp') - if self.dev.is_connected(): - self.logger.info("Connected to YJ Attenuator controller at %s:%d", self.host, self.port) - return {"ok": True, "message": "Connected to controller"} - else: - self.logger.error("Failed to connect to YJ Attenuator controller") - return {"ok": False, "error": "Failed to connect to controller"} + self.controller.connect(host=self.ip_address, port=self.tcp_port, con_type='tcp') except Exception as e: - self.logger.error("Exception while connecting to YJ Attenuator controller: %s", str(e)) - return {"ok": False, "error": f"Failed to connect to controller {str(e)}"} + self.logger.error("Exception while connecting: %s", str(e)) + return {"ok": False, "error": f"Connection failed: {str(e)}"} + + if self.controller.is_connected(): + self.logger.info("Connected to YJ Attenuator DD100-MC controller at %s:%d", + self.ip_address, self.tcp_port) + return {"ok": True, "message": "Connection successful"} + else: + self.logger.error("Failed to connect to YJ Attenuator DD100-MC controller") + return {"ok": False, "error": "Connection failed"} def disconnect(self): """ handles disconnection from the device """ - if not self.state['connected']: + if not self.controller.is_connected(): return {"ok": True, "message": "Already disconnected"} try: - self.dev.disconnect() - if not self.dev.is_connected(): - self.logger.info("Disconnected from YJ Attenuator controller") - return {"ok": True, "message": "Disconnected from controller"} - else: - self.logger.error("Failed to disconnect from YJ Attenuator controller") - return {"ok": False, "error": "Failed to disconnect from controller"} + self.controller.disconnect() except Exception as e: - self.logger.error("Exception while disconnecting from YJ Attenuator controller: %s", str(e)) - return {"ok": False, "error": f"Failed to disconnect from controller {str(e)}"} + self.logger.error("Exception while disconnecting: %s", str(e)) + return {"ok": False, "error": f"Failed to disconnect, {str(e)}"} + + if not self.controller.is_connected(): + self.logger.info("Disconnected from YJ Attenuator DD100-MC controller") + return {"ok": True, "message": "Disconnected successfully"} + else: + self.logger.error("Failed to disconnect from YJ Attenuator DD100-MC controller") + return {"ok": False, "error": "Failed to disconnect"} def initialize(self): """ initializes the device (i.e. homing) """ - if self.dev.initialize(): - self.logger.info("Initialized YJ Attenuator controller") + if self.controller.initialize(): + self.logger.info("Initialized YJ Attenuator DD100-MC controller") return {"ok": True, "message": "Successfully homed device"} else: - self.logger.error("Failed to initialize YJ Attenuator controller") + self.logger.error("Failed to initialize YJ Attenuator DD100-MC controller") return {"ok": False, "error": "Failed to home device"} def reset(self): """ resets the device """ - ret = self.dev.reset() + ret = self.controller.reset() if ret is not None: self.logger.info("Reset YJ Attenuator controller") return {"ok": True, "message": "Successfully reset device", "reset": ret} @@ -144,7 +136,7 @@ def reset(self): def get_stage_params(self): """ gets the device params """ - ret = self.dev.get_params() + ret = self.controller.get_params() if ret is not None: self.logger.debug("get_params: %s", str(ret)) return {"ok": True, "params": ret} @@ -157,7 +149,7 @@ def get_atomic_value(self, item: str): if item not in ['atten','pos']: self.logger.error("Invalid item requested: %s", item) return {"ok": False, "error": "Invalid item requested"} - ret = self.dev.get_atomic_value(item) + ret = self.controller.get_atomic_value(item) if ret is None: self.logger.error(f"Failed to get {item} from YJ Attenuator controller") return {"ok": False, "error": f"Failed to get {item} value"} @@ -167,7 +159,7 @@ def get_atomic_value(self, item: str): def set_attenuation(self, attenuation: float): """ sets the attenuation value on the device """ - if self.dev.set_attenuation(attenuation): + if self.controller.set_attenuation(attenuation): self.logger.info("Successfully set attenuation to %f", attenuation) return {"ok": True, "message": "Successfully set attenuation"} else: @@ -176,7 +168,7 @@ def set_attenuation(self, attenuation: float): def set_position(self, position: int): """ sets the position value on the device """ - if self.dev.set_pos(position): + if self.controller.set_pos(position): self.logger.info("Successfully set position to %d", position) return {"ok": True, "message": "Successfully set position"} else: @@ -187,7 +179,7 @@ def step(self, direction: str): """ steps the device in the given direction """ if direction not in ['F', 'B']: return {"ok": False, "error": "Invalid direction, must be 'F' or 'B'"} - ret = self.dev.step(direction) + ret = self.controller.step(direction) if ret is not None: self.logger.info("Successfully stepped device %s to position %d", direction, ret) return {"ok": True, "message": "Successfully stepped device", "position": ret}