Add Daq.use_running_status_server as a master on/off switch

Previously the only way to stop using an already-configured status
server was setting the private _status_server = None directly - which
also throws away the configured URL, so re-enabling meant remembering
and restoring it by hand.

use_running_status_server (default True, matching current behavior)
is checked first thing in the status_client property - the single
choke point every status-server-backed callback in this class already
goes through (append_start_status_to_scan, start_scan_monitoring,
copy_aliases_to_scan, use_status_server(), ...), so gating it there
covers all of them for free. _status_server itself is left untouched,
so toggling this back to True brings back the same client, same
base_url, with nothing to re-supply.

Live-toggleable on an already-running session, no restart needed:
bernina.daq.use_running_status_server = False

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 21:12:30 +02:00
co-authored by Claude Sonnet 5
parent aedefab5cd
commit aef3f50ddb
2 changed files with 72 additions and 1 deletions
+50 -1
View File
@@ -149,6 +149,8 @@ class Daq(Assembly):
status_server_stale_timeout=20.0,
status_server_async=True,
scan_monitoring=True,
use_running_status_server=True,
speak_run_number=False,
):
super().__init__(name=name)
self.channels = {}
@@ -259,6 +261,21 @@ class Daq(Assembly):
# None (default) keeps the existing, unchanged local behaviour.
self._status_server = status_server
self._status_server_client = None
# Master on/off switch, independent of _status_server itself: unlike
# setting _status_server = None, toggling this off and back on again
# does not lose the configured URL/client - status_client (and so
# every status-server-backed callback, all of which go through it)
# just returns None while it is False, exactly as if none were
# configured. Live-toggleable (bernina.daq.use_running_status_server
# = False), not just a constructor default, for the same "switch it
# off mid-session without restarting the namespace" reason as
# scan_monitoring above.
self.use_running_status_server = use_running_status_server
# Off by default: this is a per-instrument audible preference
# (originated as bernina.py's _message_end_scan, which spoke the run
# number via pyttsx3 on every scan end), not something every Daq
# user wants. Live-toggleable like use_running_status_server above.
self.speak_run_number = speak_run_number
# Short timeout for /health and the admin routes (so an unreachable
# server fails fast), long one for a snapshot - a full get_status()
# fan-out over ~14k bernina channels takes 10-20 s.
@@ -340,6 +357,7 @@ class Daq(Assembly):
self.end_scan_monitoring,
self.copy_scan_info_to_raw,
self.end_scan_monitors,
self.announce_run_number,
]
self.elog = elog
@@ -1331,6 +1349,29 @@ class Daq(Assembly):
# scan.daq_run_number = runno
scan._append(DetectorMemory, runno, name="daq_run_number")
def announce_run_number(self, scan=None, **kwargs):
"""
Speaks the finished run number out loud via pyttsx3. Opt-in
(self.speak_run_number, off by default) and best-effort: any failure
here (no audio device, engine busy, pyttsx3 not installed, ...) must
never fail a scan, exactly like the timetool_data_monitor alarm in
eco.bernina.bernina. Formerly bernina.py's module-level
_message_end_scan/callbacks_end_scan, from before Daq owned its own
scan callbacks.
"""
if not self.speak_run_number:
return
try:
import pyttsx3
runno = scan.daq_run_number.get_current_value()
e = pyttsx3.init()
e.say(f"Finished run {runno}.")
e.runAndWait()
e.stop()
except Exception:
pass
# get/set_dap_settings and get/set_detector_settings are NOT dead here by
# accident -- they're implemented and used, just per-detector rather than
# per-Daq: see eco.detector.jungfrau.Jungfrau.get_dap_settings/
@@ -1349,7 +1390,15 @@ class Daq(Assembly):
@property
def status_client(self):
"""The configured StatusServerClient, or None if not using one."""
"""The configured StatusServerClient, or None if not using one --
either because none is configured (_status_server is None) or
because use_running_status_server has been switched off. Every
status-server-backed callback in this class goes through this
property rather than _status_server/_status_server_client
directly, so gating it here is enough to make the switch above
cover all of them."""
if not self.use_running_status_server:
return None
if self._status_server is None:
return None
if self._status_server_client is None:
+22
View File
@@ -78,6 +78,7 @@ def _daq(status_server=None, namespace=None, strict=False):
daq.namespace = namespace
daq._status_server = status_server
daq._status_server_client = status_server
daq.use_running_status_server = True
daq.status_server_timeout = 5.0
daq.status_server_snapshot_timeout = 60.0
daq.status_server_strict = strict
@@ -121,6 +122,7 @@ def test_status_client_is_built_from_a_url_string():
daq = Daq.__new__(Daq)
daq._status_server = "http://host:8091/"
daq._status_server_client = None
daq.use_running_status_server = True
daq.status_server_timeout = 3.0
daq.status_server_snapshot_timeout = 60.0
client = daq.status_client
@@ -132,9 +134,28 @@ def test_no_status_server_means_no_client():
daq = Daq.__new__(Daq)
daq._status_server = None
daq._status_server_client = None
daq.use_running_status_server = True
assert daq.status_client is None
def test_use_running_status_server_false_overrides_a_configured_server():
"""The master switch (bernina.daq.use_running_status_server = False) --
unlike setting _status_server = None, this must not lose the
configured URL: flip it back to True and the same client (same
base_url) comes back, not a freshly-built one asked all over again."""
daq = Daq.__new__(Daq)
daq._status_server = "http://host:8091/"
daq._status_server_client = None
daq.use_running_status_server = False
daq.status_server_timeout = 3.0
daq.status_server_snapshot_timeout = 60.0
assert daq.status_client is None
daq.use_running_status_server = True
client = daq.status_client
assert client.base_url == "http://host:8091"
def test_init_namespace_uses_server_and_skips_local_init(capsys):
ns = FakeNamespace()
daq = _daq(status_server=FakeStatusClient(), namespace=ns)
@@ -253,6 +274,7 @@ def test_snapshot_and_health_get_separate_timeouts():
daq = Daq.__new__(Daq)
daq._status_server = "http://host:8091"
daq._status_server_client = None
daq.use_running_status_server = True
daq.status_server_timeout = 10.0
daq.status_server_snapshot_timeout = 180.0
client = daq.status_client