diff --git a/finesse/hardware/plugins/sensors/decades.py b/finesse/hardware/plugins/sensors/decades.py index 245a4938..f4625a60 100644 --- a/finesse/hardware/plugins/sensors/decades.py +++ b/finesse/hardware/plugins/sensors/decades.py @@ -97,6 +97,7 @@ class Decades( "documentation." ), }, + async_open=True, ): """A class for monitoring a DECADES sensor server.""" @@ -119,14 +120,13 @@ def __init__( self._params: list[DecadesParameter] """Parameters returned by the server.""" + super().__init__(poll_interval) + # Obtain full parameter list in order to parse received data self.obtain_parameter_list( frozenset(params.split(",")) if params else frozenset() ) - # We only want to start polling after we have loaded the parameter list - super().__init__(poll_interval, start_polling=False) - def obtain_parameter_list(self, params: Set[str]) -> None: """Request the parameter list from the DECADES server and wait for response.""" self._requester.make_request( @@ -204,5 +204,9 @@ def _on_params_received(self, reply: QNetworkReply, params: Set[str]) -> None: else: self._params = list(_get_selected_params(all_params_info, params)) + # Tell the frontend that the device is ready + self.signal_is_opened() + # Now we have enough information to start parsing sensor readings self.start_polling() + self.request_readings() diff --git a/finesse/hardware/plugins/sensors/em27_sensors.py b/finesse/hardware/plugins/sensors/em27_sensors.py index 6d46fa27..9b907190 100644 --- a/finesse/hardware/plugins/sensors/em27_sensors.py +++ b/finesse/hardware/plugins/sensors/em27_sensors.py @@ -50,7 +50,11 @@ class EM27Error(Exception): """Indicates than an error occurred while parsing the webpage.""" -class EM27SensorsBase(SensorsBase, class_type=DeviceClassType.IGNORE): +class EM27SensorsBase( + SensorsBase, + class_type=DeviceClassType.IGNORE, + async_open=True, +): """An interface for monitoring EM27 properties.""" def __init__(self, url: str, poll_interval: float = float("nan")) -> None: @@ -62,9 +66,12 @@ def __init__(self, url: str, poll_interval: float = float("nan")) -> None: """ self._url: str = url self._requester = HTTPRequester() + self._connected = False super().__init__(poll_interval) + self.request_readings() + def request_readings(self) -> None: """Request the EM27 property data from the web server. @@ -87,6 +94,12 @@ def _on_reply_received(self, reply: QNetworkReply) -> None: data: bytes = reply.readAll().data() content = data.decode() readings = get_em27_sensor_data(content) + + if not self._connected: + self._connected = True + self.signal_is_opened() + self.start_polling() + self.send_readings_message(readings) diff --git a/finesse/hardware/plugins/sensors/sensors_base.py b/finesse/hardware/plugins/sensors/sensors_base.py index ac069ea7..103a6572 100644 --- a/finesse/hardware/plugins/sensors/sensors_base.py +++ b/finesse/hardware/plugins/sensors/sensors_base.py @@ -23,15 +23,12 @@ class SensorsBase( calling send_readings_message() with new sensor values (at some point). """ - def __init__( - self, poll_interval: float = float("nan"), start_polling: bool = True - ) -> None: + def __init__(self, poll_interval: float = float("nan")) -> None: """Create a new SensorsBase. Args: poll_interval: How often to poll the sensor device (seconds). If set to nan, the device will only be polled once on device open - start_polling: Whether to start polling the device immediately """ super().__init__() @@ -39,19 +36,11 @@ def __init__( self._poll_timer.timeout.connect(self.request_readings) self._poll_interval = poll_interval - if start_polling: - self.start_polling() - def start_polling(self) -> None: """Begin polling the device.""" if not isnan(self._poll_interval): self._poll_timer.start(int(self._poll_interval * 1000)) - # Poll device once on open. - # TODO: Run this synchronously so we can check that things work before the - # device.opened message is sent - self.request_readings() - @abstractmethod def request_readings(self) -> None: """Request new sensor readings from the device.""" diff --git a/finesse/hardware/plugins/spectrometer/opus_interface.py b/finesse/hardware/plugins/spectrometer/opus_interface.py index dc8217c8..346ceff2 100644 --- a/finesse/hardware/plugins/spectrometer/opus_interface.py +++ b/finesse/hardware/plugins/spectrometer/opus_interface.py @@ -71,6 +71,7 @@ class OPUSInterface( "This is rate limited to around one request every two seconds by OPUS." ), }, + async_open=True, ): """Interface for communicating with the OPUS program. @@ -95,7 +96,7 @@ def __init__( self._url = f"http://{host}:{port}/opusrs" """URL to make requests.""" - self._status = SpectrometerStatus.UNDEFINED + self._status: SpectrometerStatus | None = None """The last known status of the spectrometer.""" self._status_timer = QTimer() self._status_timer.timeout.connect(self._request_status) @@ -121,6 +122,10 @@ def _on_reply_received(self, reply: QNetworkReply) -> None: # If the status has changed, notify listeners if new_status != self._status: + # On first update, we need to signal that the device is now open + if self._status is None: + self.signal_is_opened() + self._status = new_status self.send_status_message(new_status) diff --git a/tests/hardware/plugins/sensors/test_sensors_base.py b/tests/hardware/plugins/sensors/test_sensors_base.py index 3f6b9e94..04362c5c 100644 --- a/tests/hardware/plugins/sensors/test_sensors_base.py +++ b/tests/hardware/plugins/sensors/test_sensors_base.py @@ -16,37 +16,29 @@ def device(timer_mock: Mock) -> SensorsBase: class _MockSensorsDevice(SensorsBase, description="Mock sensors device"): - def __init__(self, poll_interval: float = float("nan"), start_polling=True): + def __init__(self, poll_interval: float = float("nan")): self.request_readings_mock = MagicMock() - super().__init__(poll_interval, start_polling) + super().__init__(poll_interval) def request_readings(self) -> None: self.request_readings_mock() -@pytest.mark.parametrize("start_polling", (False, True)) @patch("finesse.hardware.plugins.sensors.sensors_base.QTimer") -def test_init(timer_mock: Mock, start_polling: bool) -> None: +def test_init(timer_mock: Mock) -> None: """Test for the constructor.""" - with patch.object(_MockSensorsDevice, "start_polling") as start_mock: - device = _MockSensorsDevice(1.0, start_polling) - assert device._poll_interval == 1.0 - timer = cast(Mock, device._poll_timer) - timer.timeout.connect.assert_called_once_with(device.request_readings) - - if start_polling: - start_mock.assert_called_once_with() - else: - start_mock.assert_not_called() + device = _MockSensorsDevice(1.0) + assert device._poll_interval == 1.0 + timer = cast(Mock, device._poll_timer) + timer.timeout.connect.assert_called_once_with(device.request_readings) @patch("finesse.hardware.plugins.sensors.sensors_base.QTimer") def test_start_polling_oneshot(timer_mock: Mock) -> None: """Test the start_polling() method when polling is only done once.""" - device = _MockSensorsDevice(start_polling=False) + device = _MockSensorsDevice() device.start_polling() - device.request_readings_mock.assert_called_once_with() timer = cast(Mock, device._poll_timer) timer.start.assert_not_called() @@ -54,10 +46,9 @@ def test_start_polling_oneshot(timer_mock: Mock) -> None: @patch("finesse.hardware.plugins.sensors.sensors_base.QTimer") def test_start_polling_repeated(timer_mock: Mock) -> None: """Test the start_polling() method when polling is only done repeatedly.""" - device = _MockSensorsDevice(1.0, start_polling=False) + device = _MockSensorsDevice(1.0) device.start_polling() - device.request_readings_mock.assert_called_once_with() timer = cast(Mock, device._poll_timer) timer.start.assert_called_once_with(1000) @@ -68,7 +59,6 @@ def test_init_no_timer(timer_mock: Mock) -> None: device = _MockSensorsDevice() timer = cast(Mock, device._poll_timer) timer.start.assert_not_called() - device.request_readings_mock.assert_called_once_with() def test_send_readings_message(device: SensorsBase) -> None: diff --git a/tests/hardware/plugins/spectrometer/test_opus_interface.py b/tests/hardware/plugins/spectrometer/test_opus_interface.py index 7e712924..0ffcb05b 100644 --- a/tests/hardware/plugins/spectrometer/test_opus_interface.py +++ b/tests/hardware/plugins/spectrometer/test_opus_interface.py @@ -36,7 +36,7 @@ def test_init(timer_mock: Mock, subscribe_mock: Mock) -> None: assert opus._url == f"http://{DEFAULT_OPUS_HOST}:{DEFAULT_OPUS_PORT}/opusrs" status_mock.assert_called_once_with() - assert opus._status == SpectrometerStatus.UNDEFINED + assert opus._status is None timer.setSingleShot.assert_called_once_with(True) timer.setInterval.assert_called_once_with(1000) timer.timeout.connect.assert_called_once_with(opus._request_status)