From ee19525a14d0905a1344c5a48ba57dd18baf7925 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 19 Mar 2026 14:47:05 +0000 Subject: [PATCH] Add pu00 utilities --- python/src/streaming_data_types/__init__.py | 3 + .../pulse_metadata_pu00/Pu00Message.py | 119 ++++++++++++++++++ .../fbschemas/pulse_metadata_pu00/__init__.py | 0 .../pulse_metadata_pu00.py | 44 +++++++ python/tests/test_pu00.py | 49 ++++++++ rust/src/flatbuffers_generated/mod.rs | 18 --- rust/src/lib.rs | 5 + 7 files changed, 220 insertions(+), 18 deletions(-) create mode 100644 python/src/streaming_data_types/fbschemas/pulse_metadata_pu00/Pu00Message.py create mode 100644 python/src/streaming_data_types/fbschemas/pulse_metadata_pu00/__init__.py create mode 100644 python/src/streaming_data_types/pulse_metadata_pu00.py create mode 100644 python/tests/test_pu00.py diff --git a/python/src/streaming_data_types/__init__.py b/python/src/streaming_data_types/__init__.py index cbcb897..0cbc8fa 100644 --- a/python/src/streaming_data_types/__init__.py +++ b/python/src/streaming_data_types/__init__.py @@ -17,6 +17,7 @@ from streaming_data_types.run_stop_6s4t import deserialise_6s4t, serialise_6s4t from streaming_data_types.status_x5f2 import deserialise_x5f2, serialise_x5f2 from streaming_data_types.units_un00 import serialise_un00, deserialise_un00 +from streaming_data_types.pulse_metadata_pu00 import serialise_pu00, deserialise_pu00 __version__ = version @@ -37,6 +38,7 @@ "ad00": serialise_ad00, "da00": serialise_da00, "un00": serialise_un00, + "pu00": serialise_pu00, } @@ -56,4 +58,5 @@ "ad00": deserialise_ad00, "da00": deserialise_da00, "un00": deserialise_un00, + "pu00": deserialise_pu00, } diff --git a/python/src/streaming_data_types/fbschemas/pulse_metadata_pu00/Pu00Message.py b/python/src/streaming_data_types/fbschemas/pulse_metadata_pu00/Pu00Message.py new file mode 100644 index 0000000..40138cc --- /dev/null +++ b/python/src/streaming_data_types/fbschemas/pulse_metadata_pu00/Pu00Message.py @@ -0,0 +1,119 @@ +# automatically generated by the FlatBuffers compiler, do not modify + +# namespace: + +import flatbuffers +from flatbuffers.compat import import_numpy +np = import_numpy() + +class Pu00Message(object): + __slots__ = ['_tab'] + + @classmethod + def GetRootAs(cls, buf, offset=0): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = Pu00Message() + x.Init(buf, n + offset) + return x + + @classmethod + def GetRootAsPu00Message(cls, buf, offset=0): + """This method is deprecated. Please switch to GetRootAs.""" + return cls.GetRootAs(buf, offset) + @classmethod + def Pu00MessageBufferHasIdentifier(cls, buf, offset, size_prefixed=False): + return flatbuffers.util.BufferHasIdentifier(buf, offset, b"\x70\x75\x30\x30", size_prefixed=size_prefixed) + + # Pu00Message + def Init(self, buf, pos): + self._tab = flatbuffers.table.Table(buf, pos) + + # Pu00Message + def SourceName(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + # Pu00Message + def MessageId(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int64Flags, o + self._tab.Pos) + return 0 + + # Pu00Message + def ReferenceTime(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Int64Flags, o + self._tab.Pos) + return 0 + + # Pu00Message + def Vetos(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return None + + # Pu00Message + def PeriodNumber(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return None + + # Pu00Message + def ProtonCharge(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) + if o != 0: + return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos) + return None + +def Pu00MessageStart(builder): + builder.StartObject(6) + +def Start(builder): + Pu00MessageStart(builder) + +def Pu00MessageAddSourceName(builder, sourceName): + builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(sourceName), 0) + +def AddSourceName(builder, sourceName): + Pu00MessageAddSourceName(builder, sourceName) + +def Pu00MessageAddMessageId(builder, messageId): + builder.PrependInt64Slot(1, messageId, 0) + +def AddMessageId(builder, messageId): + Pu00MessageAddMessageId(builder, messageId) + +def Pu00MessageAddReferenceTime(builder, referenceTime): + builder.PrependInt64Slot(2, referenceTime, 0) + +def AddReferenceTime(builder, referenceTime): + Pu00MessageAddReferenceTime(builder, referenceTime) + +def Pu00MessageAddVetos(builder, vetos): + builder.PrependUint32Slot(3, vetos, None) + +def AddVetos(builder, vetos): + Pu00MessageAddVetos(builder, vetos) + +def Pu00MessageAddPeriodNumber(builder, periodNumber): + builder.PrependUint32Slot(4, periodNumber, None) + +def AddPeriodNumber(builder, periodNumber): + Pu00MessageAddPeriodNumber(builder, periodNumber) + +def Pu00MessageAddProtonCharge(builder, protonCharge): + builder.PrependFloat32Slot(5, protonCharge, None) + +def AddProtonCharge(builder, protonCharge): + Pu00MessageAddProtonCharge(builder, protonCharge) + +def Pu00MessageEnd(builder): + return builder.EndObject() + +def End(builder): + return Pu00MessageEnd(builder) diff --git a/python/src/streaming_data_types/fbschemas/pulse_metadata_pu00/__init__.py b/python/src/streaming_data_types/fbschemas/pulse_metadata_pu00/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/src/streaming_data_types/pulse_metadata_pu00.py b/python/src/streaming_data_types/pulse_metadata_pu00.py new file mode 100644 index 0000000..3b94469 --- /dev/null +++ b/python/src/streaming_data_types/pulse_metadata_pu00.py @@ -0,0 +1,44 @@ +from collections import namedtuple +import flatbuffers + +import streaming_data_types.fbschemas.pulse_metadata_pu00.Pu00Message as Pu00Message +from streaming_data_types.utils import check_schema_identifier + +FILE_IDENTIFIER = b"pu00" + +PulseMetadata = namedtuple("PulseMetadata", ("source_name", "message_id", "timestamp_ns", "vetos", "period_number", "proton_charge")) + + +def deserialise_pu00(buffer) -> PulseMetadata: + check_schema_identifier(buffer, FILE_IDENTIFIER) + pu00 = Pu00Message.Pu00Message.GetRootAsPu00Message(buffer, 0) + + return PulseMetadata( + source_name=pu00.SourceName().decode("utf-8"), + message_id=pu00.MessageId(), + timestamp_ns=pu00.ReferenceTime(), + period_number=pu00.PeriodNumber(), + proton_charge=pu00.ProtonCharge(), + vetos=pu00.Vetos(), + ) + + +def serialise_pu00(source_name: str, message_id: int, timestamp_ns: int, vetos: int | None, period_number: int | None, proton_charge: float | None) -> bytes: + builder = flatbuffers.Builder(128) + + source_offset = builder.CreateString(source_name) + + Pu00Message.Pu00MessageStart(builder) + Pu00Message.AddSourceName(builder, source_offset) + Pu00Message.AddMessageId(builder, message_id) + Pu00Message.AddReferenceTime(builder, timestamp_ns) + if vetos is not None: + Pu00Message.AddVetos(builder, vetos) + if period_number is not None: + Pu00Message.AddPeriodNumber(builder, period_number) + if proton_charge is not None: + Pu00Message.AddProtonCharge(builder, proton_charge) + result = Pu00Message.Pu00MessageEnd(builder) + + builder.Finish(result, file_identifier=FILE_IDENTIFIER) + return bytes(builder.Output()) diff --git a/python/tests/test_pu00.py b/python/tests/test_pu00.py new file mode 100644 index 0000000..33c4fc0 --- /dev/null +++ b/python/tests/test_pu00.py @@ -0,0 +1,49 @@ +import pytest + +from streaming_data_types import DESERIALISERS, SERIALISERS +from streaming_data_types.exceptions import WrongSchemaException +from streaming_data_types.pulse_metadata_pu00 import deserialise_pu00, serialise_pu00 + + +class TestSerialisationPu00: + def test_serialises_and_deserialises_pu00_message_correctly(self): + """ + Round-trip to check what we serialise is what we get back. + """ + buf = serialise_pu00("some_source", 123, 456, vetos=1, period_number=2, proton_charge=3.) + entry = deserialise_pu00(buf) + + assert entry.source_name == "some_source" + assert entry.message_id == 123 + assert entry.timestamp_ns == 456 + assert entry.vetos == 1 + assert entry.period_number == 2 + assert entry.proton_charge == pytest.approx(3.) + + def test_serialises_and_deserialises_un00_message_correctly_with_none(self): + """ + Round-trip to check what we serialise is what we get back with None specified as a unit. + """ + buf = serialise_pu00("some_source", 123, 456, None, None, None) + entry = deserialise_pu00(buf) + + assert entry.source_name == "some_source" + assert entry.message_id == 123 + assert entry.timestamp_ns == 456 + assert entry.vetos is None + assert entry.period_number is None + assert entry.proton_charge is None + + def test_if_buffer_has_wrong_id_then_throws(self): + buf = serialise_pu00("some_source", 1234567890, 123, None, None, None) + + # Manually hack the id + buf = bytearray(buf) + buf[4:8] = b"1234" + + with pytest.raises(WrongSchemaException): + deserialise_pu00(buf) + + def test_schema_type_is_in_global_serialisers_list(self): + assert "pu00" in SERIALISERS + assert "pu00" in DESERIALISERS diff --git a/rust/src/flatbuffers_generated/mod.rs b/rust/src/flatbuffers_generated/mod.rs index b664214..9aedece 100644 --- a/rust/src/flatbuffers_generated/mod.rs +++ b/rust/src/flatbuffers_generated/mod.rs @@ -1,54 +1,36 @@ #[path = "6s4t_run_stop.rs"] pub mod run_stop_6s4t; - #[path = "ad00_area_detector_array.rs"] pub mod area_detector_array_ad00; - #[path = "al00_alarm.rs"] pub mod alarm_al00; - #[path = "answ_action_response.rs"] pub mod action_response_answ; - #[path = "da00_dataarray.rs"] pub mod dataarray_da00; - #[path = "df12_det_spec_map.rs"] pub mod det_spec_map_df12; - #[path = "ep01_epics_connection.rs"] pub mod epics_connection_ep01; - #[path = "ev44_events.rs"] pub mod events_ev44; - #[path = "f144_logdata.rs"] pub mod logdata_f144; - #[path = "fc00_forwarder_config.rs"] pub mod forwarder_config_fc00; - #[path = "hs01_event_histogram.rs"] pub mod event_histogram_hs01; - #[path = "json_json.rs"] pub mod json_json; - #[path = "pl72_run_start.rs"] pub mod run_start_pl72; - #[path = "pu00_pulse_metadata.rs"] pub mod pulse_metadata_pu00; - #[path = "se00_data.rs"] pub mod data_se00; - #[path = "un00_units.rs"] pub mod units_un00; - #[path = "wrdn_finished_writing.rs"] pub mod finished_writing_wrdn; - #[path = "x5f2_status.rs"] pub mod status_x5f2; - diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 065212b..35a1a7a 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -21,6 +21,7 @@ use crate::flatbuffers_generated::forwarder_config_fc00::{ }; use crate::flatbuffers_generated::json_json::{JsonData, root_as_json_data}; use crate::flatbuffers_generated::logdata_f144::{f144_LogData, root_as_f_144_log_data}; +use crate::flatbuffers_generated::pulse_metadata_pu00::{Pu00Message, root_as_pu_00_message}; use crate::flatbuffers_generated::run_start_pl72::{RunStart, root_as_run_start}; use crate::flatbuffers_generated::run_stop_6s4t::{RunStop, root_as_run_stop}; use crate::flatbuffers_generated::status_x5f2::{Status, root_as_status}; @@ -37,6 +38,7 @@ pub mod flatbuffers_generated; #[derive(Debug, Clone, PartialEq)] pub enum DeserializedMessage<'a> { EventDataEv44(Event44Message<'a>), + PulseMetadataPu00(Pu00Message<'a>), AreaDetectorAd00(ad00_ADArray<'a>), RunStartPl72(RunStart<'a>), RunStop6s4t(RunStop<'a>), @@ -83,6 +85,9 @@ pub fn deserialize_message(data: &[u8]) -> Result, Deser Some(b"ev44") => Ok(DeserializedMessage::EventDataEv44( root_as_event_44_message(data)?, )), + Some(b"pu00") => Ok(DeserializedMessage::PulseMetadataPu00( + root_as_pu_00_message(data)?, + )), Some(b"ad00") => Ok(DeserializedMessage::AreaDetectorAd00( root_as_ad_00_adarray(data)?, )),