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
37 changes: 37 additions & 0 deletions mnc/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@
6: 9800000,
7: 19600000}

_BEAM_RECORDERS = tuple(f'dr{n}' for n in range(1, 11)) + tuple(f'drt{n}' for n in range(1, 3))


def _recording_path_from_response(response):
"""Return absolute data file path from a record command response, or None."""
try:
payload = response['response']
filename = payload['filename']
except (KeyError, TypeError):
return None
if os.path.isabs(filename):
return filename
directory = payload.get('directory')
if directory:
return os.path.join(directory, filename)
return filename


class Controller():
""" Parse configuration and control all subsystems in uniform manner.
Ideally, will also make it easy to monitor basic system status.
Expand Down Expand Up @@ -473,6 +491,13 @@ def start_dr(self, recorders=None, t0='now', duration=None, time_avg=1, teng_f1=
teng_f1/2 are the central frequencies of t-engine tunings in units of Hz.
f0 sets bandwidth as integer from 1 (250kHz) to 7 (19.6MHz).
gain1/2 are t-engine re-quantization gains from 0 (most gain) to 15 (least gain).

Returns
-------
dict
Successful beam recordings keyed by recorder name. Each value has
``status``, ``path``, ``filename``, and ``directory``. Recorders
without a filename are omitted.
"""

dconf = self.conf['dr']
Expand Down Expand Up @@ -500,6 +525,7 @@ def start_dr(self, recorders=None, t0='now', duration=None, time_avg=1, teng_f1=

# start ms writing
logger.info(f"Starting recorders {recorders} at {start.mjd} (currently {Time.now().mjd})")
recordings = {}
for recorder in recorders:
# treat encoded recorder name "drt1raw" interpreted as "drt1" with a "raw_record" command.
record_command = "raw_record" if "raw" in recorder else "record"
Expand Down Expand Up @@ -564,12 +590,23 @@ def start_dr(self, recorders=None, t0='now', duration=None, time_avg=1, teng_f1=
except (KeyError, TypeError):
pass
logger.info(f"recording on {recorder}{rec_extra_info}")
if recorder in _BEAM_RECORDERS:
path = _recording_path_from_response(response)
if path is not None:
recordings[recorder] = {
'status': 'success',
'path': path,
'filename': os.path.basename(path),
'directory': os.path.dirname(path) or '.',
}
else:
logger.warn(f"recording on {recorder} failed: {response['response']}")

if self.drc.read_monitor_point('summary', recorder).value != 'normal':
self.drc.read_monitor_point('info', recorder)

return recordings

def status_dr(self, recorders=None):
""" Print data recorder info monitor point
"""
Expand Down
116 changes: 106 additions & 10 deletions tests/test_control.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,111 @@
import sys
import unittest
from unittest.mock import patch
from mnc.control import Controller
from unittest.mock import MagicMock, patch

class TestController(unittest.TestCase):
def setUp(self):
self.controller = Controller()
for _mod in (
"lwa_f",
"lwa_f.snap2_feng_etcd_client",
"lwa_f.snap2_fengine",
"lwa_f.helpers",
"lwa352_pipeline_control",
"observing",
"observing.obsstate",
):
sys.modules.setdefault(_mod, MagicMock())

def test_init_with_args(self):
controller = Controller(recorders='dr5')
self.assertEqual(controller.conf['dr']['recorders'], ['dr5'])
from mnc.control import Controller, _recording_path_from_response

if __name__ == '__main__':

class TestRecordingPathFromResponse(unittest.TestCase):
def test_absolute_filename(self):
response = {"response": {"filename": "/lustre/ubuntu/beam01/D1_123.dat"}}
self.assertEqual(
_recording_path_from_response(response),
"/lustre/ubuntu/beam01/D1_123.dat",
)

def test_relative_filename_volt(self):
response = {
"response": {
"filename": "D1_123.dat",
"directory": "/lustre/ubuntu/beam01",
}
}
self.assertEqual(
_recording_path_from_response(response),
"/lustre/ubuntu/beam01/D1_123.dat",
)

def test_relative_filename_power(self):
response = {
"response": {
"filename": "D1_123.dat",
"directory": "/lustre/pipeline/beam03",
}
}
self.assertEqual(
_recording_path_from_response(response),
"/lustre/pipeline/beam03/D1_123.dat",
)

def test_missing_filename(self):
self.assertIsNone(_recording_path_from_response({"response": {}}))


class TestStartDrRecordings(unittest.TestCase):
def test_start_dr_returns_beam_recording_paths(self):
controller = Controller.__new__(Controller)
controller.conf = {"dr": {"recorders": ["dr3"]}}
controller.drvnums = []

response = {
"status": "success",
"response": {
"filename": "D1_123.dat",
"directory": "/lustre/pipeline/beam03",
},
}
controller.drc = MagicMock()
controller.drc.send_command.return_value = (True, response)
summary = MagicMock()
summary.value = "normal"
controller.drc.read_monitor_point.return_value = summary

recordings = controller.start_dr(
recorders=["dr3"],
duration=60000,
time_avg=100,
)

self.assertEqual(
recordings,
{
"dr3": {
"status": "success",
"path": "/lustre/pipeline/beam03/D1_123.dat",
"filename": "D1_123.dat",
"directory": "/lustre/pipeline/beam03",
}
},
)

def test_start_dr_omits_drvs(self):
controller = Controller.__new__(Controller)
controller.conf = {"dr": {"recorders": ["drvs"]}}
controller.drvnums = []

response = {"status": "success", "response": {}}
controller.drc = MagicMock()
controller.drc.send_command.return_value = (True, response)
summary = MagicMock()
summary.value = "normal"
controller.drc.read_monitor_point.return_value = summary

with patch.object(controller, "stop_dr"):
recordings = controller.start_dr(recorders=["drvs"])

self.assertEqual(recordings, {})


if __name__ == "__main__":
unittest.main()

Loading