Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/hscal/hscal_hketatten.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# HISPEC RED CAL Etalon Attenuator — Standa SMC8 contoller instance
# HISPEC RED CAL Etalon Attenuator — Standa SMC8 controller instance
#
# Usage:
# daemons/hscal/smc8_attenuator -c config/hscal/hscal_hketatten.yaml
Expand Down
15 changes: 15 additions & 0 deletions config/hscal/hscal_yjetatten.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# HISPEC BLUE CAL Etalon Attenuator — OZOptics DD100MC controller instance
#
# Usage:
# daemons/hscal/ozopt_attenuator -c config/hscal/hscal_yjetatten.yaml

peer_id: yjetatten
group_id: hscal

hardware:
ip_address: 192.168.29.153
tcp_port: 10003

logging:
level: INFO
file: /tmp/hscal_hketatten.log
135 changes: 135 additions & 0 deletions daemons/hscal/ozopt_attenuator
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env python3
"""
OZOptics Attenuator Daemon

Exposes the OZOptics attenuator functions as libby keywords.
"""

import argparse
import sys

from hispec import HispecDriverDaemon
from hispec.driver.ozoptics.dd100mc import OZController

# StageController.home() polls to completion, so the RPC waits out the move
HOME_TIMEOUT_S = 60.0


class OZDaemon(HispecDriverDaemon): # pylint: disable=W0223
"""Daemon for controlling the OZ Optics attenuator via keywords."""

def __init__(self):
super().__init__()

self.ip_address = None
self.tcp_port = None
self.controller = OZController(log=True)
self.units = "steps" # units of the position keyword
self.atten_units = "dB" # fixed by the driver's conversion coefficients

def on_start(self):
"""Called when the daemon starts - register keywords and connect."""
# from_config populates _config after __init__, so read it here
self.ip_address = self.get_config("hardware.ip_address")
self.tcp_port = self.get_config("hardware.tcp_port")

self.logger.info("Starting %s daemon", self.peer_id)

self.keyword_registry.bool("isconnected",
getter=self.controller.is_connected,
setter=self._set_connected,
description="ADC rotator controller is connected.")
self.keyword_registry.string("status",
getter=self._status,
description="Status of the device driver as a "
"(code, message) string.")
self.keyword_registry.trigger("shutdown",
action=self._shutdown,
description="Gracefully stop this daemon; "
"refused while a stage is moving.")
self.keyword_registry.bool("isreferenced",
getter=self.is_referenced,
setter=self.controller.home(),
timeout_s=HOME_TIMEOUT_S,
description="OZOptic attenuator stage is referenced; "
"write true to home.")
self.keyword_registry.float(f"position",
getter=self.controller.get_pos,
setter=self.controller.set_pos,
units=self.units,
description="OZOptics stage position in steps.")
self.keyword_registry.float(f"attenuation",
getter=self.controller.get_attenuation,
setter=self.controller.set_attenuation,
units=self.atten_units,
description="OZOptics attenuation in dB.")
self.keyword_registry.string("configuration",
getter=self.controller.get_params,
description="OZOptics attenuator configuration parameters.")

if not (self.ip_address and self.tcp_port):
self.logger.error("No IP address or port specified")
return

try:
self._connect_hardware()
self.logger.info("Daemon started successfully and connected to hardware")
except Exception as e: # pylint: disable=W0718
self.logger.error("Failed to connect to hardware: %s", e)
self.logger.warning("Daemon will start but hardware is not available")

def on_stop(self):
"""Cleanup when the daemon shuts down."""
self.logger.info("Shutting down %s daemon", self.peer_id)
try:
self.controller.disconnect()
self.logger.info("Disconnected from ADC controller")
except Exception as e: # pylint: disable=W0718
self.logger.error("Error disconnecting: %s", e)

def _connect_hardware(self):
self.logger.info("Connecting to OZOptics controller at %s:%s",
self.ip_address, self.tcp_port)
self.controller.connect(host=self.ip_address, port=int(self.tcp_port))
# The drivers signal failure by staying disconnected rather than raising
if not self.controller.is_connected():
raise RuntimeError(
f"no connection to {self.ip_address}:{self.tcp_port}")
self.controller.initialize()

def _set_connected(self, value: bool) -> None:
if value:
self._connect_hardware()
else:
self.controller.disconnect()

def is_referenced(self) -> bool:
"""Return True if the stage has been referenced (homed)."""
return bool(self.controller.is_homed())


def main():
"""Main entry point for the daemon."""
parser = argparse.ArgumentParser(description='OZOptics Attenuator Daemon')
parser.add_argument('-c', '--config', type=str,
help='Path to config file (YAML or JSON)')

args = parser.parse_args()

if not args.config:
print("--config is required", file=sys.stderr)
sys.exit(2)

try:
daemon = OZDaemon.from_config_file(args.config)
daemon.serve()
except KeyboardInterrupt:
print("\nDaemon interrupted by user")
sys.exit(0)
except Exception as e: # pylint: disable=W0718
print(f"Error running daemon: {e}", file=sys.stderr)
sys.exit(1)


if __name__ == '__main__':
main()
9 changes: 9 additions & 0 deletions systemd/instances/hscal_yjetatten.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# HISPEC BLUE CAL Etalon Attenuator — OZOptics DD100MC controller instance (hscal)
#
# hispec deploy hscal_yjetatten
#
# copies this file to /etc/hispec/instances/hscal_yjetatten.env and
# config/hscal/hscal_yjetatten.yaml to /etc/hispec/hscal_yjetatten.yaml, then
# enables and starts the daemon.
HISPEC_DAEMON=hscal/ozopt_attenuator
HISPEC_HOST=hispecserver
Loading