def test_create_update_handler_throws_if_protocol_not_specified():
    producer = FakeProducer()
    channel_with_no_protocol = Channel(
        "name", EpicsProtocol.NONE, "output_topic", "f142"
    )
    with pytest.raises(RuntimeError):
        create_update_handler(producer, None, None, channel_with_no_protocol, 20000)  # type: ignore
def test_update_handler_publishes_int_update(pv_value, pv_caproto_type,
                                             pv_numpy_type):
    producer = FakeProducer()
    context = FakeContext()

    pv_source_name = "source_name"
    update_handler = CAUpdateHandler(producer, context, pv_source_name,
                                     "output_topic", "f142")  # type: ignore

    metadata = (0, 0, TimeStamp(4, 0))
    context.call_monitor_callback_with_fake_pv_update(
        ReadNotifyResponse(
            np.array([pv_value]).astype(pv_numpy_type),
            pv_caproto_type,
            1,
            1,
            1,
            metadata=metadata,
        ))

    assert producer.published_payload is not None
    pv_update_output = deserialise_f142(producer.published_payload)
    assert pv_update_output.value == pv_value
    assert pv_update_output.source_name == pv_source_name

    update_handler.stop()
def test_update_handler_throws_if_schema_not_recognised():
    producer = FakeProducer()
    context = FakeContext()
    non_existing_schema = "DOESNTEXIST"
    with pytest.raises(ValueError):
        PVAUpdateHandler(producer, context, "source_name", "output_topic",
                         non_existing_schema)  # type: ignore
Exemplo n.º 4
0
def test_update_handlers_can_be_removed_by_schema(update_handlers, ):
    status_reporter = StubStatusReporter()
    producer = FakeProducer()

    schema_1 = "f142"
    schema_2 = "tdct"
    update_handlers[Channel("", EpicsProtocol.NONE, "",
                            schema_1)] = StubUpdateHandler()  # type: ignore
    update_handlers[Channel("", EpicsProtocol.NONE, "",
                            schema_2)] = StubUpdateHandler()  # type: ignore
    update_handlers[Channel("", EpicsProtocol.NONE, "",
                            schema_1)] = StubUpdateHandler()  # type: ignore

    config_update = ConfigUpdate(
        CommandType.REMOVE,
        (Channel(None, EpicsProtocol.NONE, None, schema_1), ),
    )

    handle_configuration_change(config_update, 20000, None, update_handlers,
                                producer, None, None, _logger,
                                status_reporter)  # type: ignore
    assert len(update_handlers) == 1
    assert (
        list(update_handlers.keys())[0].schema == schema_2
    ), "Expected handlers for schema_1 to have been removed, leaving only one for schema_2"
Exemplo n.º 5
0
def test_update_handlers_can_be_removed_by_topic(update_handlers, ):
    status_reporter = StubStatusReporter()
    producer = FakeProducer()

    topic_name_1 = "topic_1"
    topic_name_2 = "topic_2"
    update_handlers[Channel("", EpicsProtocol.NONE, topic_name_1,
                            None)] = StubUpdateHandler()  # type: ignore
    update_handlers[Channel("", EpicsProtocol.NONE, topic_name_2,
                            None)] = StubUpdateHandler()  # type: ignore
    update_handlers[Channel("", EpicsProtocol.NONE, topic_name_1,
                            None)] = StubUpdateHandler()  # type: ignore

    config_update = ConfigUpdate(
        CommandType.REMOVE,
        (Channel(None, EpicsProtocol.NONE, topic_name_1, None), ),
    )

    handle_configuration_change(config_update, 20000, None, update_handlers,
                                producer, None, None, _logger,
                                status_reporter)  # type: ignore
    assert len(update_handlers) == 1
    assert (
        list(update_handlers.keys())[0].output_topic == topic_name_2
    ), "Expected handlers for topic_1 to have been removed, leaving only one for topic_2"
def test_update_handler_publishes_alarm_update():
    producer = FakeProducer()
    context = FakeContext()

    pv_value = 42
    pv_caproto_type = ChannelType.TIME_INT
    pv_numpy_type = np.int32
    pv_source_name = "source_name"
    alarm_status = 6  # AlarmStatus.LOW
    alarm_severity = 1  # AlarmSeverity.MINOR

    update_handler = CAUpdateHandler(producer, context, pv_source_name,
                                     "output_topic", "f142")  # type: ignore

    metadata = (alarm_status, alarm_severity, TimeStamp(4, 0))
    context.call_monitor_callback_with_fake_pv_update(
        ReadNotifyResponse(
            np.array([pv_value]).astype(pv_numpy_type),
            pv_caproto_type,
            1,
            1,
            1,
            metadata=metadata,
        ))

    assert producer.published_payload is not None
    pv_update_output = deserialise_f142(producer.published_payload)
    assert pv_update_output.value == pv_value
    assert pv_update_output.source_name == pv_source_name
    assert pv_update_output.alarm_status == AlarmStatus.LOW
    assert pv_update_output.alarm_severity == AlarmSeverity.MINOR

    update_handler.stop()
def test_update_handler_publishes_periodic_update():
    producer = FakeProducer()
    context = FakeContext()

    pv_value = 42
    pv_caproto_type = ChannelType.TIME_INT
    pv_numpy_type = np.int32
    pv_source_name = "source_name"

    update_period_ms = 10
    update_handler = CAUpdateHandler(producer, context, pv_source_name,
                                     "output_topic", "f142",
                                     update_period_ms)  # type: ignore
    metadata = (0, 0, TimeStamp(4, 0))
    context.call_monitor_callback_with_fake_pv_update(
        ReadNotifyResponse(
            np.array([pv_value]).astype(pv_numpy_type),
            pv_caproto_type,
            1,
            1,
            1,
            metadata=metadata,
        ))

    assert producer.published_payload is not None
    pv_update_output = deserialise_f142(producer.published_payload)
    assert pv_update_output.value == pv_value
    assert pv_update_output.source_name == pv_source_name

    sleep(0.05)
    assert (
        producer.messages_published > 1
    ), "Expected more than the 1 message from triggered update due to periodic updates being active"

    update_handler.stop()
Exemplo n.º 8
0
def test_wildcard_cannot_be_used_to_remove_channels_by_schema(
    update_handlers, ):
    # No wildcard matching on schemas because ? and * are allowed characters in schema identifiers

    status_reporter = StubStatusReporter()
    producer = FakeProducer()

    test_channel_3 = Channel("channel_3", EpicsProtocol.NONE, "topic_3",
                             "f142")
    update_handlers[Channel("channel_1", EpicsProtocol.NONE, "topic_1",
                            "f142")] = StubUpdateHandler()  # type: ignore
    update_handlers[Channel("channel_2", EpicsProtocol.NONE, "topic_2",
                            "f142")] = StubUpdateHandler()  # type: ignore
    update_handlers[test_channel_3] = StubUpdateHandler()  # type: ignore

    config_update = ConfigUpdate(
        CommandType.REMOVE,
        (Channel(None, EpicsProtocol.NONE, None, "f?42"), ),
    )

    handle_configuration_change(config_update, 20000, None, update_handlers,
                                producer, None, None, _logger,
                                status_reporter)  # type: ignore
    assert (
        len(update_handlers) == 3
    ), "Expected no channels to be removed as ? is not treated as a wildcard when matching schemas"
Exemplo n.º 9
0
def test_update_handlers_can_be_removed_by_name_and_schema(update_handlers, ):
    status_reporter = StubStatusReporter()
    producer = FakeProducer()

    schema_1 = "f142"
    schema_2 = "tdct"
    channel_name_1 = "channel_1"
    channel_name_2 = "channel_2"
    test_channel_3 = Channel(channel_name_2, EpicsProtocol.NONE, None,
                             schema_1)
    update_handlers[Channel(channel_name_1, EpicsProtocol.NONE, None,
                            schema_1)] = StubUpdateHandler()  # type: ignore
    update_handlers[Channel(channel_name_2, EpicsProtocol.NONE, None,
                            schema_2)] = StubUpdateHandler()  # type: ignore
    update_handlers[test_channel_3] = StubUpdateHandler()  # type: ignore

    # Only the handler with the channel matching provided name AND schema should be removed
    config_update = ConfigUpdate(
        CommandType.REMOVE,
        (test_channel_3, ),
    )

    handle_configuration_change(config_update, 20000, None, update_handlers,
                                producer, None, None, _logger,
                                status_reporter)  # type: ignore
    assert len(update_handlers) == 2
    assert test_channel_3 not in update_handlers.keys()
Exemplo n.º 10
0
def test_can_add_multiple_channels_with_same_name_if_protocol_topic_or_schema_are_different(
    update_handlers, ):
    status_reporter = StubStatusReporter()
    producer = FakeProducer()
    channel_name = "test_channel"
    test_channel_1 = Channel(channel_name, EpicsProtocol.FAKE, "output_topic",
                             "f142")
    test_channel_2 = Channel(channel_name, EpicsProtocol.FAKE,
                             "output_topic_2", "f142")
    test_channel_3 = Channel(channel_name, EpicsProtocol.FAKE, "output_topic",
                             "tdct")
    config_update = ConfigUpdate(
        CommandType.ADD,
        (
            test_channel_1,
            test_channel_2,
            test_channel_3,
        ),
    )

    handle_configuration_change(config_update, 20000, None, update_handlers,
                                producer, None, None, _logger,
                                status_reporter)  # type: ignore
    assert len(update_handlers) == 3
    assert test_channel_1 in update_handlers.keys()
    assert test_channel_2 in update_handlers.keys()
    assert test_channel_3 in update_handlers.keys()
Exemplo n.º 11
0
def test_when_update_handlers_exist_their_channel_names_are_reported_in_status(
):
    test_channel_name_1 = "test_channel_name_1"
    test_channel_name_2 = "test_channel_name_2"

    # Normally the values in this dictionary are the update handler objects
    # but the StatusReporter only uses the keys
    update_handlers = {
        Channel(test_channel_name_1, EpicsProtocol.NONE, None, None): 1,
        Channel(test_channel_name_2, EpicsProtocol.NONE, None, None): 2,
    }

    fake_producer = FakeProducer()
    status_reporter = StatusReporter(update_handlers, fake_producer,
                                     "status_topic", "", "version",
                                     logger)  # type: ignore
    status_reporter.report_status()

    if fake_producer.published_payload is not None:
        deserialised_payload = deserialise_x5f2(
            fake_producer.published_payload)
        produced_status_message = json.loads(deserialised_payload.status_json)
    # Using set comprehension as order is unimportant
    assert {
        stream["channel_name"]
        for stream in produced_status_message["streams"]
    } == {
        test_channel_name_1,
        test_channel_name_2,
    }, "Expected channel names for existing update handlers to be reported in the status message"
Exemplo n.º 12
0
def test_identical_configurations_are_not_added(update_handlers, ):
    status_reporter = StubStatusReporter()
    producer = FakeProducer()
    channel_name = "test_channel"
    test_channel_1 = Channel(channel_name, EpicsProtocol.FAKE, "output_topic",
                             "f142")
    test_channel_2 = Channel(channel_name, EpicsProtocol.FAKE, "output_topic",
                             "f142")
    test_channel_3 = Channel(channel_name, EpicsProtocol.FAKE, "output_topic",
                             "f142")
    config_update = ConfigUpdate(
        CommandType.ADD,
        (
            test_channel_1,
            test_channel_2,
            test_channel_3,
        ),
    )

    handle_configuration_change(config_update, 20000, None, update_handlers,
                                producer, None, None, _logger,
                                status_reporter)  # type: ignore
    assert (
        len(update_handlers) == 1
    ), "Only expect one channel to be added as others requested were identical"
    assert test_channel_1 in update_handlers.keys()
Exemplo n.º 13
0
def test_configuration_stored_when_channels_added(update_handlers, ):
    status_reporter = StubStatusReporter()
    producer = FakeProducer()
    channel_name = "test_channel"
    test_channel_1 = Channel(channel_name, EpicsProtocol.FAKE, "output_topic",
                             "f142")
    test_channel_2 = Channel(channel_name, EpicsProtocol.FAKE, "output_topic",
                             "f142")
    test_channel_3 = Channel(channel_name, EpicsProtocol.FAKE, "output_topic",
                             "f142")
    config_update = ConfigUpdate(
        CommandType.ADD,
        (
            test_channel_1,
            test_channel_2,
            test_channel_3,
        ),
    )
    config_store = mock.create_autospec(ConfigurationStore)

    handle_configuration_change(config_update, 20000, None, update_handlers,
                                producer, None, None, _logger, status_reporter,
                                config_store)  # type: ignore

    config_store.save_configuration.assert_called_once()
Exemplo n.º 14
0
def test_update_handler_publishes_enum_update():
    producer = FakeProducer()
    context = FakeContext()

    pv_index = 0
    pv_value_str = "choice0"
    pv_timestamp_s = 1.1  # seconds from unix epoch
    pv_source_name = "source_name"

    pva_update_handler = PVAUpdateHandler(producer, context, pv_source_name,
                                          "output_topic",
                                          "f142")  # type: ignore
    context.call_monitor_callback_with_fake_pv_update(
        NTEnum(valueAlarm=True).wrap(
            {
                "index": pv_index,
                "choices": [pv_value_str, "choice1", "choice2"]
            },
            timestamp=pv_timestamp_s,
        ))

    assert producer.published_payload is not None
    pv_update_output = deserialise_f142(producer.published_payload)
    assert pv_update_output.value == pv_value_str
    assert pv_update_output.source_name == pv_source_name

    pva_update_handler.stop()
Exemplo n.º 15
0
def test_update_handler_publishes_periodic_update():
    producer = FakeProducer()
    context = FakeContext()

    pv_timestamp_s = 1.1  # seconds from unix epoch
    pv_source_name = "source_name"
    pv_value = -3
    pv_type = "i"

    update_period_ms = 10
    pva_update_handler = PVAUpdateHandler(producer, context, pv_source_name,
                                          "output_topic", "f142",
                                          update_period_ms)  # type: ignore
    context.call_monitor_callback_with_fake_pv_update(
        NTScalar(pv_type, valueAlarm=True).wrap(pv_value,
                                                timestamp=pv_timestamp_s))

    assert producer.published_payload is not None
    pv_update_output = deserialise_f142(producer.published_payload)
    assert pv_update_output.value == pv_value
    assert pv_update_output.source_name == pv_source_name

    sleep(0.05)
    assert (
        producer.messages_published > 1
    ), "Expected more than the 1 message from triggered update due to periodic updates being active"

    pva_update_handler.stop()
def test_pva_handler_created_when_pva_protocol_specified():
    producer = FakeProducer()
    context = FakePVAContext()
    channel_with_pva_protocol = Channel(
        "name", EpicsProtocol.PVA, "output_topic", "f142"
    )
    handler = create_update_handler(producer, None, context, channel_with_pva_protocol, 20000)  # type: ignore
    assert isinstance(handler, PVAUpdateHandler)
def test_create_update_handler_throws_if_channel_has_no_schema():
    producer = FakeProducer()
    channel_with_no_topic = Channel("name", EpicsProtocol.PVA, "output_topic", None)
    with pytest.raises(RuntimeError):
        create_update_handler(producer, None, None, channel_with_no_topic, 20000)  # type: ignore

    channel_with_empty_topic = Channel("name", EpicsProtocol.PVA, "output_topic", "")
    with pytest.raises(RuntimeError):
        create_update_handler(producer, None, None, channel_with_empty_topic, 20000)  # type: ignore
Exemplo n.º 18
0
def test_no_change_to_empty_update_handlers_when_malformed_config_update_handled(
    update_handlers, ):
    status_reporter = StubStatusReporter()
    producer = FakeProducer()
    config_update = ConfigUpdate(CommandType.MALFORMED, None)

    handle_configuration_change(config_update, 20000, None, update_handlers,
                                producer, None, None, _logger,
                                status_reporter)  # type: ignore
    assert not update_handlers
def test_when_multiple_pvs_dumped_config_contains_all_pv_details():
    producer = FakeProducer()
    store = ConfigurationStore(producer, consumer=None, topic="store_topic")

    store.save_configuration(CHANNELS_TO_STORE)

    stored_message = parse_config_update(
        producer.published_payload)  # type: ignore
    stored_channels = stored_message.channels

    assert_stored_channel_correct(stored_channels[0])  # type: ignore
    assert_stored_channel_correct(stored_channels[1])  # type: ignore
def test_when_no_pvs_stored_message_type_is_remove_all():
    producer = FakeProducer()
    store = ConfigurationStore(producer, consumer=None, topic="store_topic")

    store.save_configuration({})

    stored_message = parse_config_update(
        producer.published_payload)  # type: ignore

    assert stored_message.channels is None
    assert (stored_message.command_type == config_change_to_command_type[
        UpdateType.REMOVEALL])
Exemplo n.º 21
0
def test_all_update_handlers_are_removed_when_removeall_config_update_is_handled(
    update_handlers, ):
    status_reporter = StubStatusReporter()
    producer = FakeProducer()
    config_update = ConfigUpdate(CommandType.REMOVE_ALL, None)

    update_handlers[Channel("test_channel_1", EpicsProtocol.NONE, None,
                            None)] = StubUpdateHandler()  # type: ignore
    update_handlers[Channel("test_channel_2", EpicsProtocol.NONE, None,
                            None)] = StubUpdateHandler()  # type: ignore
    handle_configuration_change(config_update, 20000, None, update_handlers,
                                producer, None, None, _logger,
                                status_reporter)  # type: ignore
    assert not update_handlers
Exemplo n.º 22
0
def test_no_change_to_update_handlers_when_malformed_config_update_handled(
    update_handlers, ):
    status_reporter = StubStatusReporter()
    producer = FakeProducer()
    config_update = ConfigUpdate(CommandType.MALFORMED, None)

    existing_channel_name = "test_channel"
    update_handlers[Channel(existing_channel_name, EpicsProtocol.NONE, None,
                            None)] = StubUpdateHandler()  # type: ignore
    handle_configuration_change(config_update, 20000, None, update_handlers,
                                producer, None, None, _logger,
                                status_reporter)  # type: ignore
    assert len(update_handlers) == 1
    assert existing_channel_name in _get_channel_names(update_handlers)
def test_update_handler_publishes_tdct_update():
    producer = FakeProducer()

    pv_source_name = "source_name"
    fake_update_handler = FakeUpdateHandler(producer, pv_source_name,
                                            "output_topic", "tdct",
                                            100)  # type: ignore
    fake_update_handler._timer_callback()

    assert producer.published_payload is not None
    pv_update_output = deserialise_tdct(producer.published_payload)
    assert pv_update_output.name == pv_source_name

    fake_update_handler.stop()
Exemplo n.º 24
0
def test_status_message_contains_service_id():
    service_id = "test_service_id"
    update_handlers: Dict = {}

    fake_producer = FakeProducer()
    status_reporter = StatusReporter(update_handlers, fake_producer,
                                     "status_topic", service_id, "version",
                                     logger)  # type: ignore
    status_reporter.report_status()

    if fake_producer.published_payload is not None:
        deserialised_payload = deserialise_x5f2(
            fake_producer.published_payload)
    assert deserialised_payload.service_id == service_id
Exemplo n.º 25
0
def test_update_handler_does_not_include_alarm_details_if_unchanged_in_subsequent_updates(
):
    producer = FakeProducer()
    context = FakeContext()

    pv_timestamp_s = 1.1  # seconds from unix epoch
    pv_source_name = "source_name"
    pv_value = -3
    pv_type = "i"
    alarm_status = 4  # Indicates RECORD alarm, we map the alarm message to a specific alarm status to forward
    alarm_severity = 1  # AlarmSeverity.MINOR
    alarm_message = "HIGH_ALARM"

    pva_update_handler = PVAUpdateHandler(producer, context, pv_source_name,
                                          "output_topic",
                                          "f142")  # type: ignore
    context.call_monitor_callback_with_fake_pv_update(
        NTScalar(pv_type, valueAlarm=True).wrap(
            {
                "value": pv_value,
                "alarm": {
                    "status": alarm_status,
                    "severity": alarm_severity,
                    "message": alarm_message,
                },
            },
            timestamp=pv_timestamp_s,
        ))
    # Second update, with unchanged alarm
    context.call_monitor_callback_with_fake_pv_update(
        NTScalar(pv_type, valueAlarm=True).wrap(
            {
                "value": pv_value,
                "alarm": {
                    "status": alarm_status,
                    "severity": alarm_severity,
                    "message": alarm_message,
                },
            },
            timestamp=pv_timestamp_s,
        ))

    assert producer.messages_published == 2
    pv_update_output = deserialise_f142(producer.published_payload)
    assert pv_update_output.alarm_status == AlarmStatus.NO_CHANGE
    assert pv_update_output.alarm_severity == AlarmSeverity.NO_CHANGE

    pva_update_handler.stop()
Exemplo n.º 26
0
def test_when_no_update_handlers_exist_no_streams_are_present_in_reported_status(
):
    update_handlers: Dict = {}

    fake_producer = FakeProducer()
    status_reporter = StatusReporter(update_handlers, fake_producer,
                                     "status_topic", "", "version",
                                     logger)  # type: ignore
    status_reporter.report_status()

    if fake_producer.published_payload is not None:
        deserialised_payload = deserialise_x5f2(
            fake_producer.published_payload)
        produced_status_message = json.loads(deserialised_payload.status_json)
    assert (
        len(produced_status_message["streams"]) == 0
    ), "Expected no streams in reported status message as there are no update handlers"
Exemplo n.º 27
0
def test_configuration_not_stored_when_command_is_malformed(update_handlers, ):
    status_reporter = StubStatusReporter()
    producer = FakeProducer()
    test_channel_1 = Channel("test_channel", EpicsProtocol.FAKE,
                             "output_topic", "f142")
    update_handlers[test_channel_1] = StubUpdateHandler()  # type: ignore
    config_update = ConfigUpdate(
        CommandType.MALFORMED,
        None,
    )

    config_store = mock.create_autospec(ConfigurationStore)

    handle_configuration_change(config_update, 20000, None, update_handlers,
                                producer, None, None, _logger, status_reporter,
                                config_store)  # type: ignore

    config_store.save_configuration.assert_not_called()
Exemplo n.º 28
0
def test_update_handler_publishes_enum_update():
    producer = FakeProducer()
    context = FakeContext()

    pv_caproto_type = ChannelType.TIME_ENUM
    pv_source_name = "source_name"
    update_handler = CAUpdateHandler(producer, context, pv_source_name,
                                     "output_topic", "f142")  # type: ignore

    # Nothing gets published when ENUM type update is received, the handler will resubscribe using STRING
    # type as the string is more useful to forwarder to the filewriter than the enum int
    metadata = (0, 0, TimeStamp(4, 0))
    context.call_monitor_callback_with_fake_pv_update(
        ReadNotifyResponse(
            np.array([0]),
            pv_caproto_type,
            1,
            1,
            1,
            metadata=metadata,
        ))
    # Second update, with STRING type
    enum_string_value = "ENUM_STRING"
    context.call_monitor_callback_with_fake_pv_update(
        ReadNotifyResponse(
            [enum_string_value.encode("utf8")],
            ChannelType.TIME_STRING,
            1,
            1,
            1,
            metadata=metadata,
        ))

    assert (
        producer.messages_published == 1
    ), "Only expected a single message with string payload, not the original enum update"
    assert producer.published_payload is not None
    pv_update_output = deserialise_f142(producer.published_payload)
    assert pv_update_output.value == enum_string_value
    assert pv_update_output.source_name == pv_source_name

    update_handler.stop()
Exemplo n.º 29
0
def test_update_handler_publishes_int_update(pv_value, pv_type):
    producer = FakeProducer()
    context = FakeContext()

    pv_timestamp_s = 1.1  # seconds from unix epoch
    pv_source_name = "source_name"

    pva_update_handler = PVAUpdateHandler(producer, context, pv_source_name,
                                          "output_topic",
                                          "f142")  # type: ignore
    context.call_monitor_callback_with_fake_pv_update(
        NTScalar(pv_type, valueAlarm=True).wrap(pv_value,
                                                timestamp=pv_timestamp_s))

    assert producer.published_payload is not None
    pv_update_output = deserialise_f142(producer.published_payload)
    assert pv_update_output.value == pv_value
    assert pv_update_output.source_name == pv_source_name

    pva_update_handler.stop()
Exemplo n.º 30
0
def test_update_handler_does_not_include_alarm_details_if_unchanged_in_subsequent_updates(
):
    producer = FakeProducer()
    context = FakeContext()

    pv_value = 42
    pv_caproto_type = ChannelType.TIME_INT
    pv_numpy_type = np.int32
    pv_source_name = "source_name"
    alarm_status = 6  # AlarmStatus.LOW
    alarm_severity = 1  # AlarmSeverity.MINOR

    update_handler = CAUpdateHandler(producer, context, pv_source_name,
                                     "output_topic", "f142")  # type: ignore
    metadata = (alarm_status, alarm_severity, TimeStamp(4, 0))
    context.call_monitor_callback_with_fake_pv_update(
        ReadNotifyResponse(
            np.array([pv_value]).astype(pv_numpy_type),
            pv_caproto_type,
            1,
            1,
            1,
            metadata=metadata,
        ))
    # Second update, with unchanged alarm
    context.call_monitor_callback_with_fake_pv_update(
        ReadNotifyResponse(
            np.array([pv_value]).astype(pv_numpy_type),
            pv_caproto_type,
            1,
            1,
            1,
            metadata=metadata,
        ))

    assert producer.messages_published == 2
    pv_update_output = deserialise_f142(producer.published_payload)
    assert pv_update_output.alarm_status == AlarmStatus.NO_CHANGE
    assert pv_update_output.alarm_severity == AlarmSeverity.NO_CHANGE

    update_handler.stop()