From 880f96df70bd7e369a69d4e8a73fa4b6fdbf964e Mon Sep 17 00:00:00 2001 From: Don Neill Date: Fri, 23 Oct 2026 10:19:52 -0700 Subject: [PATCH 1/8] Initial Revision (not yet working) --- daemons/hscal/srs_thermal | 266 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100755 daemons/hscal/srs_thermal diff --git a/daemons/hscal/srs_thermal b/daemons/hscal/srs_thermal new file mode 100755 index 00000000..0518ef1c --- /dev/null +++ b/daemons/hscal/srs_thermal @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +""" +Generic Stanford Research Systems model PTC10 Daemon + +Exposes the SRS PTC10 etalon temperature controller commands as libby keywords. +""" + +import argparse +import sys + +from hispec import HispecDaemon +from hispec.driver.srs.ptc10 import PTC10 + + +class SrsDaemon(HispecDaemon): # pylint: disable=W0223,too-many-instance-attributes + """Daemon for controlling the SRS via keywords.""" + + # Keyword suffix, index into the controller's (p, i, d) tuple, and label. + PID_TERMS = (("p", 0, "proportional"), + ("i", 1, "integral"), + ("d", 2, "derivative")) + + def __init__(self): + super().__init__() + + self.ip_address = None + self.tcp_port = None + self.units = "C" + self.srs_model = "PTC10" + self.sensors = None + self.heaters = None + self.error = None + self.start_time = None + + # The controller needs hardware settings that from_config only populates + # after __init__ returns, so it is built in on_start instead. + self.controller = None + + def on_start(self, libby): + """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.sensors = self.get_config("sensors") or [] + self.heaters = self.get_config("heaters") or [] + + self.controller = PTC10(log=True) + + self.logger.info("Starting %s daemon with for model %s in degrees C", + self.peer_id, self.srs_model) + self.logger.info("Using sensors: %s", self.sensors) + self.logger.info("Using heaters: %s", self.heaters) + + self._register_sensor_keywords() + self._register_heater_keywords() + + self.keyword_registry.bool("isconnected", + getter=self.controller.is_connected, + setter=self._set_connected, + description="SRS controller is connected.") + self.keyword_registry.string("error", + getter=self._error_string, + description="Last error from the SRS controller.") + self.keyword_registry.string("status", + getter=self._status, + description="Status of the SRS as a (code, message) string.") + self.keyword_registry.trigger("shutdown", + action=self._shutdown, + description="Shutdown the daemon now.") + + 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") + if self.controller.initialize(): + self.logger.info("Hardware successfully initialized") + else: + self.logger.error("Hardware failed to initialize") + except Exception as e: # pylint: disable=W0718 + self.logger.error("Failed to connect to hardware: %s", e) + self.error = str(e) + self.logger.warning("Daemon will start but hardware is not available") + + def _register_sensor_keywords(self): + """Register one read-only temperature keyword per configured sensor.""" + for entry in self.sensors: + channel = entry.get("channel") + keyword = entry.get("keyword") + if not channel or not keyword: + self.logger.error("Skipping sensor entry without channel/keyword: %s", entry) + continue + + self.keyword_registry.float( + keyword, + getter=self._make_temperature_getter(channel), + units=self.units, + description=f"Temperature at sensor {channel}.") + + def _register_heater_keywords(self): + """Register the setpoint, output, status and PID keywords per heater.""" + for entry in self.heaters: + output = entry.get("id") + keyword = entry.get("keyword") + if output is None or not keyword: + self.logger.error("Skipping heater entry without id/keyword: %s", entry) + continue + + self.keyword_registry.float( + keyword, + getter=self._make_setpoint_getter(output), + setter=self._make_setpoint_setter(output), + units=self.units, + description=f"Setpoint for heater output {output}.") + self.keyword_registry.float( + f"{keyword}_output", + getter=self._make_output_getter(output), + units="percent", + description=f"Output power of heater output {output}.") + self.keyword_registry.string( + f"{keyword}_status", + getter=self._make_status_getter(output), + description=f"Status of heater output {output}.") + + for term, index, label in self.PID_TERMS: + self.keyword_registry.float( + f"{keyword}_{term}", + getter=self._make_pid_getter(output, index), + setter=self._make_pid_setter(output, index), + description=f"PID {label} term for heater output {output}.") + + def _make_temperature_getter(self, channel): + """Return a zero-argument getter reading the temperature at `channel`.""" + def getter(): + return self.controller.get_temperature(channel) + return getter + + def _make_setpoint_getter(self, output): + """Return a zero-argument getter reading the setpoint of heater `output`.""" + def getter(): + return self.controller.get_heater_setpoint(output) + return getter + + def _make_setpoint_setter(self, output): + """Return a one-argument setter writing the setpoint of heater `output`.""" + def setter(value): + if not self.controller.set_heater_setpoint(output, value): + raise RuntimeError(f"Failed to set setpoint for heater output {output}") + return setter + + def _make_output_getter(self, output): + """Return a zero-argument getter reading the power of heater `output`.""" + def getter(): + return self.controller.get_heater_output(output) + return getter + + def _make_status_getter(self, output): + """Return a zero-argument getter reading the status of heater `output`.""" + def getter(): + return self.controller.get_heater_status(output) + return getter + + def _make_pid_getter(self, output, index): + """Return a zero-argument getter reading one PID term of heater `output`.""" + def getter(): + values = self.controller.get_heater_pid(output) + return None if values is None else values[index] + return getter + + def _make_pid_setter(self, output, index): + """Return a one-argument setter writing one PID term of heater `output`. + + The controller writes P, I and D as a single command and rejects the + write command unless all three are in range, so the other two terms are read + back and resent unchanged. + """ + def setter(value): + values = self.controller.get_heater_pid(output) + if values is None: + raise RuntimeError( + f"Unable to read PID values for heater output {output}") + terms = list(values) + terms[index] = value + if not self.controller.set_heater_pid(output, *terms): + raise RuntimeError( + f"Failed to set PID values for heater output {output}") + return setter + + def _shutdown(self): + """Shutdown the daemon""" + self.logger.info("Shutting down") + if self.controller is not None: + self.controller.disconnect() + self.request_stop() + + def _connected(self) -> bool: + """True when the driver holds an open session to the SRS controller.""" + return self.controller is not None and bool(self.controller.is_connected()) + + def _status(self): + """Handles status.""" + if not self._connected(): + return str((-1, "ERROR: Not connected to hardware")) + return str(self.controller.get_status()) + + def on_stop(self, libby=None): + """Cleanup when the daemon shuts down.""" + self.logger.info("Shutting down %s daemon", self.peer_id) + if self.controller is None: + return + try: + self.controller.disconnect() + self.logger.info("Disconnected from SRS controller") + except Exception as e: # pylint: disable=W0718 + self.logger.error("Error disconnecting: %s", e) + self.error = str(e) + + def _connect_hardware(self): + self.logger.info("Connecting to SRS 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 _error_string(self) -> str: + """Return last error string""" + return self.error + + +def main(): + """Main entry point for the daemon.""" + parser = argparse.ArgumentParser(description='Generic SRS 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 = SrsDaemon.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() From 2d30281f74a2a4b5520b4f181b0de3877866e809 Mon Sep 17 00:00:00 2001 From: Don Neill Date: Mon, 26 Oct 2026 10:44:07 -0700 Subject: [PATCH 2/8] Update (still not working). --- daemons/hscal/srs_thermal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemons/hscal/srs_thermal b/daemons/hscal/srs_thermal index 0518ef1c..a95f570d 100755 --- a/daemons/hscal/srs_thermal +++ b/daemons/hscal/srs_thermal @@ -135,7 +135,7 @@ class SrsDaemon(HispecDaemon): # pylint: disable=W0223,too-many-instance-attrib def _make_temperature_getter(self, channel): """Return a zero-argument getter reading the temperature at `channel`.""" def getter(): - return self.controller.get_temperature(channel) + return self.controller.get_atomic_value(channel) return getter def _make_setpoint_getter(self, output): From 710a035196087bba54a806e123485b48548a7c0c Mon Sep 17 00:00:00 2001 From: Don Neill Date: Fri, 30 Oct 2026 14:06:54 -0700 Subject: [PATCH 3/8] Update srs submodule --- src/hispec/driver/srs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hispec/driver/srs b/src/hispec/driver/srs index 565e8333..de51351f 160000 --- a/src/hispec/driver/srs +++ b/src/hispec/driver/srs @@ -1 +1 @@ -Subproject commit 565e8333b8e526b85131776ee23f6feefa058c89 +Subproject commit de51351fce764fa82b9b9e4fec59debcd4aafcec From e58cd364eb2b5d2ba7dd2533ce3eb7fd3c5e4524 Mon Sep 17 00:00:00 2001 From: Don Neill Date: Fri, 30 Oct 2026 14:27:27 -0700 Subject: [PATCH 4/8] Code keywords for SRS PTC10 --- daemons/hscal/srs_thermal | 56 +++------------------------------------ 1 file changed, 4 insertions(+), 52 deletions(-) diff --git a/daemons/hscal/srs_thermal b/daemons/hscal/srs_thermal index a95f570d..0bc03908 100755 --- a/daemons/hscal/srs_thermal +++ b/daemons/hscal/srs_thermal @@ -15,11 +15,6 @@ from hispec.driver.srs.ptc10 import PTC10 class SrsDaemon(HispecDaemon): # pylint: disable=W0223,too-many-instance-attributes """Daemon for controlling the SRS via keywords.""" - # Keyword suffix, index into the controller's (p, i, d) tuple, and label. - PID_TERMS = (("p", 0, "proportional"), - ("i", 1, "integral"), - ("d", 2, "derivative")) - def __init__(self): super().__init__() @@ -118,19 +113,8 @@ class SrsDaemon(HispecDaemon): # pylint: disable=W0223,too-many-instance-attrib self.keyword_registry.float( f"{keyword}_output", getter=self._make_output_getter(output), - units="percent", + units="Amps", description=f"Output power of heater output {output}.") - self.keyword_registry.string( - f"{keyword}_status", - getter=self._make_status_getter(output), - description=f"Status of heater output {output}.") - - for term, index, label in self.PID_TERMS: - self.keyword_registry.float( - f"{keyword}_{term}", - getter=self._make_pid_getter(output, index), - setter=self._make_pid_setter(output, index), - description=f"PID {label} term for heater output {output}.") def _make_temperature_getter(self, channel): """Return a zero-argument getter reading the temperature at `channel`.""" @@ -141,54 +125,22 @@ class SrsDaemon(HispecDaemon): # pylint: disable=W0223,too-many-instance-attrib def _make_setpoint_getter(self, output): """Return a zero-argument getter reading the setpoint of heater `output`.""" def getter(): - return self.controller.get_heater_setpoint(output) + return self.controller.get_setpoint(output) return getter def _make_setpoint_setter(self, output): """Return a one-argument setter writing the setpoint of heater `output`.""" def setter(value): - if not self.controller.set_heater_setpoint(output, value): + if not self.controller.set_setpoint(output, value): raise RuntimeError(f"Failed to set setpoint for heater output {output}") return setter def _make_output_getter(self, output): """Return a zero-argument getter reading the power of heater `output`.""" def getter(): - return self.controller.get_heater_output(output) - return getter - - def _make_status_getter(self, output): - """Return a zero-argument getter reading the status of heater `output`.""" - def getter(): - return self.controller.get_heater_status(output) + return self.controller.get_atomic_value(output) return getter - def _make_pid_getter(self, output, index): - """Return a zero-argument getter reading one PID term of heater `output`.""" - def getter(): - values = self.controller.get_heater_pid(output) - return None if values is None else values[index] - return getter - - def _make_pid_setter(self, output, index): - """Return a one-argument setter writing one PID term of heater `output`. - - The controller writes P, I and D as a single command and rejects the - write command unless all three are in range, so the other two terms are read - back and resent unchanged. - """ - def setter(value): - values = self.controller.get_heater_pid(output) - if values is None: - raise RuntimeError( - f"Unable to read PID values for heater output {output}") - terms = list(values) - terms[index] = value - if not self.controller.set_heater_pid(output, *terms): - raise RuntimeError( - f"Failed to set PID values for heater output {output}") - return setter - def _shutdown(self): """Shutdown the daemon""" self.logger.info("Shutting down") From 79c49d0cc6ff56566a95f3c0e1a9346808c894c9 Mon Sep 17 00:00:00 2001 From: Don Neill Date: Fri, 30 Oct 2026 15:18:17 -0700 Subject: [PATCH 5/8] Updates --- config/hscal/hscal_hkettherm.yaml | 2 +- config/hscal/hscal_yjettherm.yaml | 2 +- daemons/hscal/srs_thermal | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/hscal/hscal_hkettherm.yaml b/config/hscal/hscal_hkettherm.yaml index af50f90b..6123fd5e 100644 --- a/config/hscal/hscal_hkettherm.yaml +++ b/config/hscal/hscal_hkettherm.yaml @@ -1,7 +1,7 @@ # CAL RED (HK) Etalon SRS PTC10 daemon config # # Usage: -# daemons/generic/srstherm -c config/hscal/hscal_hkettherm.yaml +# daemons/generic/srs_thermal -c config/hscal/hscal_hkettherm.yaml peer_id: hkettherm group_id: hscal diff --git a/config/hscal/hscal_yjettherm.yaml b/config/hscal/hscal_yjettherm.yaml index 9d0839bc..373bb8f0 100644 --- a/config/hscal/hscal_yjettherm.yaml +++ b/config/hscal/hscal_yjettherm.yaml @@ -1,7 +1,7 @@ # CAL BLUE (YJ) Etalon SRS PTC10 daemon config # # Usage: -# daemons/generic/srstherm -c config/hscal/hscal_yjettherm.yaml +# daemons/generic/srs_thermal -c config/hscal/hscal_yjettherm.yaml peer_id: yjettherm group_id: hscal diff --git a/daemons/hscal/srs_thermal b/daemons/hscal/srs_thermal index 0bc03908..d97bd94e 100755 --- a/daemons/hscal/srs_thermal +++ b/daemons/hscal/srs_thermal @@ -58,8 +58,8 @@ class SrsDaemon(HispecDaemon): # pylint: disable=W0223,too-many-instance-attrib getter=self._error_string, description="Last error from the SRS controller.") self.keyword_registry.string("status", - getter=self._status, - description="Status of the SRS as a (code, message) string.") + getter=self._status, + description="Status of the SRS as a (code, message) string.") self.keyword_registry.trigger("shutdown", action=self._shutdown, description="Shutdown the daemon now.") @@ -105,7 +105,7 @@ class SrsDaemon(HispecDaemon): # pylint: disable=W0223,too-many-instance-attrib continue self.keyword_registry.float( - keyword, + f"{keyword}_setpoint", getter=self._make_setpoint_getter(output), setter=self._make_setpoint_setter(output), units=self.units, @@ -114,7 +114,7 @@ class SrsDaemon(HispecDaemon): # pylint: disable=W0223,too-many-instance-attrib f"{keyword}_output", getter=self._make_output_getter(output), units="Amps", - description=f"Output power of heater output {output}.") + description=f"Heater output in amps: {output}.") def _make_temperature_getter(self, channel): """Return a zero-argument getter reading the temperature at `channel`.""" From c0cd59764ee5b2756f820986780fff3540aa5b71 Mon Sep 17 00:00:00 2001 From: Don Neill Date: Fri, 30 Oct 2026 15:46:41 -0700 Subject: [PATCH 6/8] Remove spurious srsthermal daemon and update daemons.md and config files. --- config/hscal/hscal_hkettherm.yaml | 2 +- config/hscal/hscal_yjettherm.yaml | 2 +- daemons/generic/srsthermal | 180 ------------------------------ daemons/hscal/srs_thermal | 2 +- docs/architecture/daemons.md | 4 +- 5 files changed, 5 insertions(+), 185 deletions(-) delete mode 100755 daemons/generic/srsthermal diff --git a/config/hscal/hscal_hkettherm.yaml b/config/hscal/hscal_hkettherm.yaml index 6123fd5e..8463cdb7 100644 --- a/config/hscal/hscal_hkettherm.yaml +++ b/config/hscal/hscal_hkettherm.yaml @@ -1,7 +1,7 @@ # CAL RED (HK) Etalon SRS PTC10 daemon config # # Usage: -# daemons/generic/srs_thermal -c config/hscal/hscal_hkettherm.yaml +# daemons/hscal/srs_thermal -c config/hscal/hscal_hkettherm.yaml peer_id: hkettherm group_id: hscal diff --git a/config/hscal/hscal_yjettherm.yaml b/config/hscal/hscal_yjettherm.yaml index 373bb8f0..e6ee59c1 100644 --- a/config/hscal/hscal_yjettherm.yaml +++ b/config/hscal/hscal_yjettherm.yaml @@ -1,7 +1,7 @@ # CAL BLUE (YJ) Etalon SRS PTC10 daemon config # # Usage: -# daemons/generic/srs_thermal -c config/hscal/hscal_yjettherm.yaml +# daemons/hscal/srs_thermal -c config/hscal/hscal_yjettherm.yaml peer_id: yjettherm group_id: hscal diff --git a/daemons/generic/srsthermal b/daemons/generic/srsthermal deleted file mode 100755 index 348d1dfa..00000000 --- a/daemons/generic/srsthermal +++ /dev/null @@ -1,180 +0,0 @@ -#!/usr/bin/env python3 -""" -Generic Stanford Research Systems PTC10 Thermal control Daemon - -Exposes the SRS PTC10 Temperature controller commands as libby keywords. -""" - -import argparse -import sys - -from hispec import HispecDaemon -from hispec.driver.srs.ptc10 import PTC10 - - -class SRSThermalDaemon(HispecDaemon): # pylint: disable=W0223,too-many-instance-attributes - """Daemon for controlling the SRS PTC10 via keywords.""" - - def __init__(self): - super().__init__() - - self.ip_address = None - self.tcp_port = None - self.units = None - self.sensors = None - self.heaters = None - self.error = None - self.start_time = None - - # The controller needs hardware settings that from_config only populates - # after __init__ returns, so it is built in on_start instead. - self.controller = None - - def on_start(self, libby): - """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.sensors = self.get_config("sensors") or [] - self.heaters = self.get_config("heaters") or [] - - self.controller = PTC10(log=True) - - self.logger.info("Starting %s daemon", self.peer_id) - self.logger.info("Using sensors: %s", self.sensors) - self.logger.info("Using heaters: %s", self.heaters) - - self._register_sensor_keywords() - self._register_heater_keywords() - - self.keyword_registry.bool("isconnected", - getter=self.controller.is_connected, - setter=self._set_connected, - description="SRS PTC10 controller is connected.") - self.keyword_registry.string("error", - getter=self._error_string, - description="Last error from the SRS PTC10 controller.") - self.keyword_registry.trigger("shutdown", - action=self._shutdown, - description="Shutdown the daemon now.") - - 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.error = str(e) - self.logger.warning("Daemon will start but hardware is not available") - - def _register_sensor_keywords(self): - """Register one read-only temperature keyword per configured sensor.""" - for entry in self.sensors: - channel = entry.get("channel") - keyword = entry.get("keyword") - if not channel or not keyword: - self.logger.error("Skipping sensor entry without channel/keyword: %s", entry) - continue - - self.keyword_registry.float( - keyword, - getter=self._make_temperature_getter(channel), - units=self.units, - description=f"Temperature at sensor {channel}.") - - def _register_heater_keywords(self): - """Register the setpoint, output, status and PID keywords per heater.""" - for entry in self.heaters: - output = entry.get("id") - keyword = entry.get("keyword") - if output is None or not keyword: - self.logger.error("Skipping heater entry without id/keyword: %s", entry) - continue - - self.keyword_registry.float( - keyword, - getter=self._make_output_getter(output), - units="Amps", - description=f"Output amps of heater output {output}.") - - def _make_temperature_getter(self, channel): - """Return a zero-argument getter reading the temperature at `channel`.""" - def getter(): - return self.controller.get_atomic_value(channel) - return getter - - def _make_output_getter(self, output): - """Return a zero-argument getter reading the power of heater `output`.""" - def getter(): - return self.controller.get_atomic_value(output) - return getter - - def _shutdown(self): - """Shutdown the daemon""" - self.logger.info("Shutting down") - if self.controller is not None: - self.controller.disconnect() - self.request_stop() - - def on_stop(self, libby=None): - """Cleanup when the daemon shuts down.""" - self.logger.info("Shutting down %s daemon", self.peer_id) - if self.controller is None: - return - try: - self.controller.disconnect() - self.logger.info("Disconnected from SRS PTC10 controller") - except Exception as e: # pylint: disable=W0718 - self.logger.error("Error disconnecting: %s", e) - self.error = str(e) - - def _connect_hardware(self): - self.logger.info("Connecting to SRS PTC10 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 _error_string(self) -> str: - """Return last error string""" - return self.error - - -def main(): - """Main entry point for the daemon.""" - parser = argparse.ArgumentParser(description='Generic SRS PTC10 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 = SRSThermalDaemon.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() diff --git a/daemons/hscal/srs_thermal b/daemons/hscal/srs_thermal index d97bd94e..4c9e9578 100755 --- a/daemons/hscal/srs_thermal +++ b/daemons/hscal/srs_thermal @@ -105,7 +105,7 @@ class SrsDaemon(HispecDaemon): # pylint: disable=W0223,too-many-instance-attrib continue self.keyword_registry.float( - f"{keyword}_setpoint", + keyword, getter=self._make_setpoint_getter(output), setter=self._make_setpoint_setter(output), units=self.units, diff --git a/docs/architecture/daemons.md b/docs/architecture/daemons.md index a706acbf..80f3b45f 100644 --- a/docs/architecture/daemons.md +++ b/docs/architecture/daemons.md @@ -19,7 +19,7 @@ their shared `pdu_base.py` base class. |---|---|---|---| | `generic/filterwheel` | `thorlabs.fw102c` | Thorlabs FW102C filter wheel | `hsfei_atcfw`, `hscal_hkcalfwheel1/2`, `hscal_hkgcellfwheel`, `hscal_yjcalfwheel1/2` | | `generic/lakeshore` | `lakeshore.lakeshore` | Lakeshore 336/224 temperature controller | `hsfei_atctherm`, `hscal_gcellheater1/2` | -| `generic/srsthermal` | `srs.ptc10` | SRS PTC10 thermal controller | `hscal_hkettherm`, `hscal_yjettherm` | +| `hscal/srs_thermal` | `srs.ptc10` | SRS PTC10 thermal controller | `hscal_hkettherm`, `hscal_yjettherm` | | `generic/inficon` | `inficon.inficonvgc502` | Inficon VGC502 vacuum gauge controller | `hsfei_atcpress` | | `hsfei/pi-daemon` | `pi.PIControllerBase` | PI C-663 / C-863 / E-754 motion controllers | `hsfei_atcl`, `hsfei_atcp`, `hsfei_feipo`, `hsfei_lsm`, `hsfei_ms` | | `hsfei/adc` | `newport.smc100pp` | Newport SMC100PP ADC prism rotators | `hsfei_adc` | @@ -170,7 +170,7 @@ aggregates likewise and one write closes every loop. A single-stage config registers the bare names directly, so a client that only cares about the mechanism as a whole uses the same keyword either way. -**Thermal daemons** (`lakeshore`, `srsthermal`) generate their keywords from +**Thermal daemons** (`lakeshore`, `srs_thermal`) generate their keywords from config. Each entry in `sensors:` produces one read-only temperature keyword under the name the config chose; each entry in `heaters:` produces a writable setpoint plus, for the Lakeshore, `_output`, `_status` and one From aeb345e15566075be4656a44d9ffa1bc9ba22ec4 Mon Sep 17 00:00:00 2001 From: Don Neill Date: Fri, 30 Oct 2026 15:51:39 -0700 Subject: [PATCH 7/8] Update instance files for srs_thermal daemons --- systemd/instances/hscal_hkettherm.env | 2 +- systemd/instances/hscal_yjettherm.env | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/systemd/instances/hscal_hkettherm.env b/systemd/instances/hscal_hkettherm.env index e62be571..21054ad6 100644 --- a/systemd/instances/hscal_hkettherm.env +++ b/systemd/instances/hscal_hkettherm.env @@ -5,5 +5,5 @@ # copies this file to /etc/hispec/instances/hscal_hkettherm.env and # config/hscal/hscal_hkettherm.yaml to /etc/hispec/hscal_hkettherm.yaml, then # enables and starts the daemon. -HISPEC_DAEMON=generic/srsthermal +HISPEC_DAEMON=hscal/srs_thermal HISPEC_HOST=hispecserver diff --git a/systemd/instances/hscal_yjettherm.env b/systemd/instances/hscal_yjettherm.env index 20e4bf39..f1addea9 100644 --- a/systemd/instances/hscal_yjettherm.env +++ b/systemd/instances/hscal_yjettherm.env @@ -5,5 +5,5 @@ # copies this file to /etc/hispec/instances/hscal_yjettherm.env and # config/hscal/hscal_yjettherm.yaml to /etc/hispec/hscal_yjettherm.yaml, then # enables and starts the daemon. -HISPEC_DAEMON=generic/srsthermal +HISPEC_DAEMON=hscal/srs_thermal HISPEC_HOST=hispecserver From b593dc0d801d4d22738c15ecb68d2a0b3c35b6c0 Mon Sep 17 00:00:00 2001 From: Don Neill Date: Fri, 30 Oct 2026 16:11:39 -0700 Subject: [PATCH 8/8] Documentation updates --- docs/architecture/daemons.md | 2 +- docs/operations/systemd.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/architecture/daemons.md b/docs/architecture/daemons.md index 80f3b45f..3e8a9e79 100644 --- a/docs/architecture/daemons.md +++ b/docs/architecture/daemons.md @@ -173,7 +173,7 @@ mechanism as a whole uses the same keyword either way. **Thermal daemons** (`lakeshore`, `srs_thermal`) generate their keywords from config. Each entry in `sensors:` produces one read-only temperature keyword under the name the config chose; each entry in `heaters:` produces a writable -setpoint plus, for the Lakeshore, `_output`, `_status` and one +setpoint and `_output` plus, for the Lakeshore, `_status` and one keyword per PID term. **`atccryo`** exposes cryocooler state — `cold_head_temp`, `reject_temp`, diff --git a/docs/operations/systemd.md b/docs/operations/systemd.md index 2a9a8890..c068fdd2 100644 --- a/docs/operations/systemd.md +++ b/docs/operations/systemd.md @@ -432,8 +432,8 @@ dependencies or `hispec@.service` changed. `/opt/hispec` belongs to | `hscal_gcellheater1` | `hispecserver` | `generic/lakeshore` | `config/hscal/hscal_gcellheater1.yaml` | | `hscal_gcellheater2` | `hispecserver` | `generic/lakeshore` | `config/hscal/hscal_gcellheater2.yaml` | | `hsfei_atctherm` | `hispecserver` | `generic/lakeshore` | `config/hsfei/hsfei_atctherm.yaml` | -| `hscal_hkettherm` | `hispecserver` | `generic/srsthermal` | `config/hscal/hscal_hkettherm.yaml` | -| `hscal_yjettherm` | `hispecserver` | `generic/srsthermal` | `config/hscal/hscal_yjettherm.yaml` | +| `hscal_hkettherm` | `hispecserver` | `hscal/srs_thermal` | `config/hscal/hscal_hkettherm.yaml` | +| `hscal_yjettherm` | `hispecserver` | `hscal/srs_thermal` | `config/hscal/hscal_yjettherm.yaml` | | `hscal_hketatten` | `hispecserver` | `hscal/smc8_attenuator` | `config/hscal/hscal_hketatten.yaml` | | `hsfei_adc` | `hispecserver` | `hsfei/adc` | `config/hsfei/hsfei_adc.yaml` | | `hsfei_atccryo` | `hispecserver` | `hsfei/atccryo` | `config/hsfei/hsfei_atccryo.yaml` |