diff --git a/devices/clients/StatisticsPacket.py b/devices/clients/StatisticsPacket.py deleted file mode 100644 index 3b656f4873152027fb579f470ade5a0904a05cda..0000000000000000000000000000000000000000 --- a/devices/clients/StatisticsPacket.py +++ /dev/null @@ -1,323 +0,0 @@ -import struct -from datetime import datetime, timezone -from typing import Tuple -import numpy - -__all__ = ["StatisticsPacket", "SSTPacket", "XSTPacket", "BSTPacket"] - -def get_bit_value(value: bytes, first_bit: int, last_bit:int=None) -> int: - """ Return bits [first_bit:last_bit] from value, and return their integer value. Bit 0 = LSB. - - For example, extracting bits 2-3 from b'01100' returns 11 binary = 3 decimal: - get_bit_value(b'01100', 2, 3) == 3 - - If 'last_bit' is not given, just the value of bit 'first_bit' is returned. """ - - # default last_bit to first_bit - if last_bit is None: - last_bit = first_bit - - return value >> first_bit & ((1 << (last_bit - first_bit + 1)) - 1) - -class StatisticsPacket(object): - """ - Models a statistics UDP packet from SDP. - - Packets are expected to be UDP payload only (so no Ethernet/IP/UDP headers). - - The following fields are exposed as properties & functions. The _raw fields come directly - from the packet, and have more user-friendly alternatives for intepretation: - - marker_raw packet marker as byte. - marker() packet marker as character. 'S' = SST, 'X' = XST, 'B' = BST - - version_id packet format version. - observation_id observation identifier. - station_id station identifier. - - source_info: bit field with input information, encoding several other properties. - antenna_band_index: antenna type. 0 = low band, 1 = high band. - nyquist_zone_index: nyquist zone of filter: - 0 = 0 -- 1/2 * t_adc Hz (low band), - 1 = 1/2 * t_adc -- t_adc Hz (high band), - 2 = t_adc -- 3/2 * t_adc Hz (high band). - t_adc: sampling clock. 0 = 160 MHz, 1 = 200 MHz. - fsub_type: sampling method. 0 = critically sampled, 1 = oversampled. - payload_error: 0 = data is ok, 1 = data is corrupted (a fault was encountered). - beam_repositioning_flag: 0 = data is ok, 1 = beam got repositioned during packet construction (BST only). - subband_calibrated_flag: 1 = subband data had subband calibration values applied, 0 = not. - gn_index: global index of FPGA that emitted this packet. - - data_id: bit field with payload information, encoding several other properties. - - nof_signal_inputs: number of inputs that contributed to data in this packet. - nof_bytes_per_statistics: word size of each statistic. - nof_statistics_per_packet: number of statistic data points in the payload. - - integration_interval_raw: integration interval, in block periods. - integration_interval(): integration interval, in seconds. - block_period_raw: block period, in nanoseconds. - block_period(): block period, in seconds. - block_serial_number: timestamp of the data, in block periods since 1970. - timestamp(): timestamp of the data, as a datetime object. - - """ - - def __init__(self, packet: bytes): - self.packet = packet - - self.unpack() - - # Only parse valid statistics packets from SDP, reject everything else - if self.marker_raw not in b'SBX': - raise ValueError("Invalid SDP statistics packet: packet marker (first byte) is {}, not one of 'SBX'.".format(self.marker)) - - def unpack(self): - """ Unpack the packet into properties of this object. """ - - # format string for the header, see unpack below - self.header_format = ">cBL HHB BHL BBH HQ" - self.header_size = struct.calcsize(self.header_format) - - # unpack fields - try: - (self.marker_raw, - self.version_id, - self.observation_id, - self.station_id, - self.source_info, - # reserved byte - _, - # integration interval, in block periods. This field is 3 bytes, big endian -- combine later - integration_interval_hi, - integration_interval_lo, - self.data_id, - self.nof_signal_inputs, - self.nof_bytes_per_statistic, - self.nof_statistics_per_packet, - self.block_period_raw, - self.block_serial_number) = struct.unpack(self.header_format, self.packet[:self.header_size]) - - self.integration_interval_raw = (integration_interval_hi << 16) + integration_interval_lo - except struct.error as e: - raise ValueError("Error parsing statistics packet") from e - - # unpack the fields we just updated - self.unpack_source_info() - self.unpack_data_id() - - def unpack_source_info(self): - """ Unpack the source_info field into properties of this object. """ - - self.antenna_band_index = get_bit_value(self.source_info, 15) - self.nyquist_zone_index = get_bit_value(self.source_info, 13, 14) - self.t_adc = get_bit_value(self.source_info, 12) - self.fsub_type = get_bit_value(self.source_info, 11) - self.payload_error = get_bit_value(self.source_info, 10) - self.beam_repositioning_flag = get_bit_value(self.source_info, 9) - self.subband_calibrated_flag = get_bit_value(self.source_info, 8) - # self.source_info 5-7 are reserved - self.gn_index = get_bit_value(self.source_info, 0, 4) - - def unpack_data_id(self): - """ Unpack the data_id field into properties of this object. """ - - # only useful in specialisations (XST/SST/BST) - pass - - def expected_size(self) -> int: - """ The size this packet should be (header + payload), according to the header. """ - - return self.header_size + self.nof_statistics_per_packet * self.nof_bytes_per_statistic - - @property - def marker(self) -> str: - """ Return the type of statistic: - - 'S' = SST - 'B' = BST - 'X' = XST - """ - - try: - return self.marker_raw.decode('ascii') - except UnicodeDecodeError: - # non-ascii (>127) character, return as binary - # - # this is typically not visible to the user, as these packets are not SDP statistics packets, - # which the constructor will refuse to accept. - return self.marker_raw - - def integration_interval(self) -> float: - """ Returns the integration interval, in seconds. """ - - # Translate to seconds using the block period - return self.integration_interval_raw * self.block_period() - - def block_period(self) -> float: - """ Return the block period, in seconds. """ - - return self.block_period_raw / 1e9 - - def timestamp(self) -> datetime: - """ Returns the timestamp of the data in this packet. - - Returns datetime.min if the block_serial_number in the packet is not set (0), - Returns datetime.max if the timestamp cannot be represented in python (likely because it is too large). """ - - try: - return datetime.fromtimestamp(self.block_serial_number * self.block_period(), timezone.utc) - except ValueError: - # Let's not barf anytime we want to print a header - return datetime.max - - def header(self) -> dict: - """ Return all the header fields as a dict. """ - - header = { - "marker": self.marker, - "version_id": self.version_id, - "observation_id": self.observation_id, - "station_id": self.station_id, - "source_info": { - "_raw": self.source_info, - "antenna_band_index": self.antenna_band_index, - "nyquist_zone_index": self.nyquist_zone_index, - "t_adc": self.t_adc, - "fsub_type": self.fsub_type, - "payload_error": self.payload_error, - "beam_repositioning_flag": self.beam_repositioning_flag, - "subband_calibrated_flag": self.subband_calibrated_flag, - "gn_index": self.gn_index, - }, - "data_id": { - "_raw": self.data_id, - }, - "integration_interval_raw": self.integration_interval_raw, - "integration_interval": self.integration_interval(), - "nof_signal_inputs": self.nof_signal_inputs, - "nof_bytes_per_statistic": self.nof_bytes_per_statistic, - "nof_statistics_per_packet": self.nof_statistics_per_packet, - "block_period_raw": self.block_period_raw, - "block_period": self.block_period(), - "block_serial_number": self.block_serial_number, - "timestamp": self.timestamp(), - } - - return header - -class SSTPacket(StatisticsPacket): - """ - Models an SST statistics UDP packet from SDP. - - The following fields are exposed as properties & functions. - - - signal_input_index: input (antenna polarisation) index for which this packet contains statistics - - payload[nof_statistics_per_packet]: SST statistics, an array of amplitudes per subband. - """ - - def __init__(self, packet): - super().__init__(packet) - - # We only parse SST packets - if self.marker != 'S': - raise Exception("Payload of SST requested of a non-SST packet. Actual packet marker is '{}', but must be 'S'.".format(self.marker)) - - def unpack_data_id(self): - super().unpack_data_id() - - self.signal_input_index = get_bit_value(self.data_id, 0, 7) - - def header(self): - header = super().header() - - header["data_id"]["signal_input_index"] = self.signal_input_index - - return header - - @property - def payload(self) -> numpy.array: - """ The payload of this packet, interpreted as SST data. """ - - # derive which and how many elements to read from the packet header - bytecount_to_unsigned_struct_type = { 1: 'B', 2: 'H', 4: 'I', 8: 'Q' } - format_str = ">{}{}".format(self.nof_statistics_per_packet, bytecount_to_unsigned_struct_type[self.nof_bytes_per_statistic]) - - return numpy.array(struct.unpack(format_str, self.packet[self.header_size:self.header_size + struct.calcsize(format_str)])) - -class XSTPacket(StatisticsPacket): - """ - Models an XST statistics UDP packet from SDP. - - The following fields are exposed as properties & functions. - - - subband_index: subband number for which this packet contains statistics. - baseline: antenna pair for which this packet contains statistics. - """ - - def __init__(self, packet): - super().__init__(packet) - - # We only parse XST packets - if self.marker != 'X': - raise Exception("Payload of XST requested of a non-XST packet. Actual packet marker is '{}', but must be 'X'.".format(self.marker)) - - def unpack_data_id(self): - super().unpack_data_id() - - self.subband_index = get_bit_value(self.data_id, 16, 24) - self.baseline = (get_bit_value(self.data_id, 8, 15), get_bit_value(self.data_id, 0, 7)) - - def header(self): - header = super().header() - - header["data_id"]["subband_index"] = self.subband_index - header["data_id"]["baseline"] = self.baseline - - return header - -class BSTPacket(StatisticsPacket): - """ - Models an BST statistics UDP packet from SDP. - - The following fields are exposed as properties & functions. - - beamlet_index: the number of the beamlet for which this packet holds statistics. - """ - - def __init__(self, packet): - super().__init__(packet) - - # We only parse BST packets - if self.marker != 'B': - raise Exception("Payload of BST requested of a non-BST packet. Actual packet marker is '{}', but must be 'B'.".format(self.marker)) - - def unpack_data_id(self): - super().unpack_data_id() - - self.beamlet_index = get_bit_value(self.data_id, 0, 15) - - def header(self): - header = super().header() - - header["data_id"]["beamlet_index"] = self.beamlet_index - - return header - -if __name__ == "__main__": - # parse one packet from stdin - import sys - import pprint - - # read all of stdin, even though we only parse the first packet. we're too lazy to intelligently decide when - # the packet is complete and can stop reading. - data = sys.stdin.buffer.read() - packet = SSTPacket(data) - - # print header & payload - pprint.pprint(packet.header()) - pprint.pprint(packet.payload) - diff --git a/devices/devices/__init__.py b/devices/devices/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/devices/devices/sdp_statistics/__init__.py b/devices/devices/sdp_statistics/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/devices/devices/sdp_statistics/statistics_packet.py b/devices/devices/sdp_statistics/statistics_packet.py index 37565935da2d6f10cd5898b6a3cc46f7dafcc259..8bc4e1e4609ba0b33e652a20f29fd8d8d1abe6c6 100644 --- a/devices/devices/sdp_statistics/statistics_packet.py +++ b/devices/devices/sdp_statistics/statistics_packet.py @@ -1,8 +1,9 @@ -from struct import unpack, calcsize +import struct from datetime import datetime, timezone import numpy -__all__ = ["StatisticsPacket"] +__all__ = ["StatisticsPacket", "SSTPacket", "XSTPacket", "BSTPacket"] + def get_bit_value(value: bytes, first_bit: int, last_bit:int=None) -> int: """ Return bits [first_bit:last_bit] from value, and return their integer value. Bit 0 = LSB. @@ -18,19 +19,117 @@ def get_bit_value(value: bytes, first_bit: int, last_bit:int=None) -> int: return value >> first_bit & ((1 << (last_bit - first_bit + 1)) - 1) + class StatisticsPacket(object): """ Models a statistics UDP packet from SDP. Packets are expected to be UDP payload only (so no Ethernet/IP/UDP headers). + + The following fields are exposed as properties & functions. The _raw fields come directly + from the packet, and have more user-friendly alternatives for intepretation: + + marker_raw packet marker as byte. + marker() packet marker as character. 'S' = SST, 'X' = XST, 'B' = BST + + version_id packet format version. + observation_id observation identifier. + station_id station identifier. + + source_info: bit field with input information, encoding several other properties. + antenna_band_index: antenna type. 0 = low band, 1 = high band. + nyquist_zone_index: nyquist zone of filter: + 0 = 0 -- 1/2 * t_adc Hz (low band), + 1 = 1/2 * t_adc -- t_adc Hz (high band), + 2 = t_adc -- 3/2 * t_adc Hz (high band). + t_adc: sampling clock. 0 = 160 MHz, 1 = 200 MHz. + fsub_type: sampling method. 0 = critically sampled, 1 = oversampled. + payload_error: 0 = data is ok, 1 = data is corrupted (a fault was encountered). + beam_repositioning_flag: 0 = data is ok, 1 = beam got repositioned during packet construction (BST only). + subband_calibrated_flag: 1 = subband data had subband calibration values applied, 0 = not. + gn_index: global index of FPGA that emitted this packet. + + data_id: bit field with payload information, encoding several other properties. + + nof_signal_inputs: number of inputs that contributed to data in this packet. + nof_bytes_per_statistics: word size of each statistic. + nof_statistics_per_packet: number of statistic data points in the payload. + + integration_interval_raw: integration interval, in block periods. + integration_interval(): integration interval, in seconds. + block_period_raw: block period, in nanoseconds. + block_period(): block period, in seconds. + block_serial_number: timestamp of the data, in block periods since 1970. + timestamp(): timestamp of the data, as a datetime object. + """ def __init__(self, packet: bytes): self.packet = packet - # Only parse valid packets - if self.marker not in 'SBX': - raise ValueError("Invalid SDP statistics packet: packet marker (first byte) is '{}', not one of 'SBX'.".format(self.marker)) + self.unpack() + + # Only parse valid statistics packets from SDP, reject everything else + if self.marker_raw not in b'SBX': + raise ValueError("Invalid SDP statistics packet: packet marker (first byte) is {}, not one of 'SBX'.".format(self.marker)) + + def unpack(self): + """ Unpack the packet into properties of this object. """ + + # format string for the header, see unpack below + self.header_format = ">cBL HHB BHL BBH HQ" + self.header_size = struct.calcsize(self.header_format) + + # unpack fields + try: + (self.marker_raw, + self.version_id, + self.observation_id, + self.station_id, + self.source_info, + # reserved byte + _, + # integration interval, in block periods. This field is 3 bytes, big endian -- combine later + integration_interval_hi, + integration_interval_lo, + self.data_id, + self.nof_signal_inputs, + self.nof_bytes_per_statistic, + self.nof_statistics_per_packet, + self.block_period_raw, + self.block_serial_number) = struct.unpack(self.header_format, self.packet[:self.header_size]) + + self.integration_interval_raw = (integration_interval_hi << 16) + integration_interval_lo + except struct.error as e: + raise ValueError("Error parsing statistics packet") from e + + # unpack the fields we just updated + self.unpack_source_info() + self.unpack_data_id() + + def unpack_source_info(self): + """ Unpack the source_info field into properties of this object. """ + + self.antenna_band_index = get_bit_value(self.source_info, 15) + self.nyquist_zone_index = get_bit_value(self.source_info, 13, 14) + self.t_adc = get_bit_value(self.source_info, 12) + self.fsub_type = get_bit_value(self.source_info, 11) + self.payload_error = get_bit_value(self.source_info, 10) + self.beam_repositioning_flag = get_bit_value(self.source_info, 9) + self.subband_calibrated_flag = get_bit_value(self.source_info, 8) + # self.source_info 5-7 are reserved + self.gn_index = get_bit_value(self.source_info, 0, 4) + + def unpack_data_id(self): + """ Unpack the data_id field into properties of this object. """ + + # only useful in specialisations (XST/SST/BST) + pass + + def expected_size(self) -> int: + """ The size this packet should be (header + payload), according to the header. """ + + return self.header_size + self.nof_statistics_per_packet * self.nof_bytes_per_statistic @property def marker(self) -> str: @@ -41,80 +140,14 @@ class StatisticsPacket(object): 'X' = XST """ - raw_marker = unpack("c",self.packet[0:1])[0] - try: - return raw_marker.decode('ascii') + return self.marker_raw.decode('ascii') except UnicodeDecodeError: # non-ascii (>127) character, return as binary # # this is typically not visible to the user, as these packets are not SDP statistics packets, # which the constructor will refuse to accept. - return raw_marker - - @property - def version_id(self) -> int: - """ Return the version of this packet. """ - - return unpack("B",self.packet[1:2])[0] - - @property - def observation_id(self) -> int: - """ Return the ID of the observation running when this packet was generated. """ - - return unpack("<I",self.packet[2:6])[0] - - @property - def station_id(self) -> int: - """ Return the number of the station this packet was generated on. """ - - return unpack("<H",self.packet[6:8])[0] - - @property - def source_info(self) -> int: - """ Return a dict with the source_info flags. The dict contains the following fields: - - _raw: raw value of the source_info field in the packet, as an integer. - antenna_band_index: antenna type. 0 = low band, 1 = high band. - nyquist_zone_index: nyquist zone of filter: - 0 = 0 -- 1/2 * t_adc Hz (low band), - 1 = 1/2 * t_adc -- t_adc Hz (high band), - 2 = t_adc -- 3/2 * t_adc Hz (high band). - t_adc: sampling clock. 0 = 160 MHz, 1 = 200 MHz. - fsub_type: sampling method. 0 = critically sampled, 1 = oversampled. - payload_error: 0 = data is ok, 1 = data is corrupted (a fault was encountered). - beam_repositioning_flag: 0 = data is ok, 1 = beam got repositioned during packet construction (BST only). - subband_calibrated_flag: 1 = subband data had subband calibration values applied, 0 = not - reserved: reserved bits - gn_index: global index of FPGA that emitted this packet. """ - - bits = unpack("<H",self.packet[8:10])[0] - - return { - "_raw": bits, - "antenna_band_index": get_bit_value(bits, 15), - "nyquist_zone_index": get_bit_value(bits, 13, 14), - "t_adc": get_bit_value(bits, 12), - "fsub_type": get_bit_value(bits, 11), - "payload_error": get_bit_value(bits, 10), - "beam_repositioning_flag": get_bit_value(bits, 9), - "subband_calibrated_flag": get_bit_value(bits, 8), - "reserved": get_bit_value(bits, 5, 7), - "gn_index": get_bit_value(bits, 0, 4), - } - - @property - def reserved(self) -> bytes: - """ Reserved bytes. """ - - return self.packet[10:11] - - @property - def integration_interval_raw(self) -> int: - """ Returns the integration interval, in blocks. """ - - # This field is 3 bytes, little endian, so we need to append a 0 to parse it as a 32-bit integer. - return unpack("<I", self.packet[11:14] + b'0')[0] + return self.marker_raw def integration_interval(self) -> float: """ Returns the integration interval, in seconds. """ @@ -122,64 +155,47 @@ class StatisticsPacket(object): # Translate to seconds using the block period return self.integration_interval_raw * self.block_period() - @property - def data_id(self) -> int: - """ Returns the generic data identifier. """ - - return unpack("<I",self.packet[14:18])[0] - - @property - def nof_signal_inputs(self) -> int: - """ Number of inputs that were used for constructing the payload. """ - return unpack("<B",self.packet[18:19])[0] - - @property - def nof_bytes_per_statistic(self) -> int: - """ Word size for the payload. """ - - return unpack("<B",self.packet[19:20])[0] - - @property - def nof_statistics_per_packet(self) -> int: - """ Number of data points in the payload. """ - - return unpack("<H",self.packet[20:22])[0] - - @property - def block_period_raw(self) -> int: - """ Return the block period, in nanoseconds. """ - - return unpack("<H",self.packet[22:24])[0] - def block_period(self) -> float: """ Return the block period, in seconds. """ return self.block_period_raw / 1e9 - @property - def block_serial_number(self) -> int: - """ Block index since epoch (1970). """ - - return unpack("<Q",self.packet[24:32])[0] - def timestamp(self) -> datetime: - """ Returns the timestamp of the data in this packet. """ + """ Returns the timestamp of the data in this packet. + + Returns datetime.min if the block_serial_number in the packet is not set (0), + Returns datetime.max if the timestamp cannot be represented in python (likely because it is too large). """ - return datetime.fromtimestamp(self.block_serial_number * self.block_period(), timezone.utc) + try: + return datetime.fromtimestamp(self.block_serial_number * self.block_period(), timezone.utc) + except ValueError: + # Let's not barf anytime we want to print a header + return datetime.max def header(self) -> dict: """ Return all the header fields as a dict. """ - return { + header = { "marker": self.marker, "version_id": self.version_id, "observation_id": self.observation_id, "station_id": self.station_id, - "source_info": self.source_info, - "reserved": self.reserved, + "source_info": { + "_raw": self.source_info, + "antenna_band_index": self.antenna_band_index, + "nyquist_zone_index": self.nyquist_zone_index, + "t_adc": self.t_adc, + "fsub_type": self.fsub_type, + "payload_error": self.payload_error, + "beam_repositioning_flag": self.beam_repositioning_flag, + "subband_calibrated_flag": self.subband_calibrated_flag, + "gn_index": self.gn_index, + }, + "data_id": { + "_raw": self.data_id, + }, "integration_interval_raw": self.integration_interval_raw, "integration_interval": self.integration_interval(), - "data_id": self.data_id, "nof_signal_inputs": self.nof_signal_inputs, "nof_bytes_per_statistic": self.nof_bytes_per_statistic, "nof_statistics_per_packet": self.nof_statistics_per_packet, @@ -189,19 +205,108 @@ class StatisticsPacket(object): "timestamp": self.timestamp(), } - @property - def payload_sst(self) -> numpy.array: - """ The payload of this packet, interpreted as SST data. """ + return header + +class SSTPacket(StatisticsPacket): + """ + Models an SST statistics UDP packet from SDP. + + The following fields are exposed as properties & functions. + + + signal_input_index: input (antenna polarisation) index for which this packet contains statistics + + payload[nof_statistics_per_packet]: SST statistics, an array of amplitudes per subband. + """ + def __init__(self, packet): + super().__init__(packet) + + # We only parse SST packets if self.marker != 'S': raise Exception("Payload of SST requested of a non-SST packet. Actual packet marker is '{}', but must be 'S'.".format(self.marker)) + def unpack_data_id(self): + super().unpack_data_id() + + self.signal_input_index = get_bit_value(self.data_id, 0, 7) + + def header(self): + header = super().header() + + header["data_id"]["signal_input_index"] = self.signal_input_index + + return header + + @property + def payload(self) -> numpy.array: + """ The payload of this packet, interpreted as SST data. """ + # derive which and how many elements to read from the packet header bytecount_to_unsigned_struct_type = { 1: 'B', 2: 'H', 4: 'I', 8: 'Q' } - format_str = "<{}{}".format(self.nof_statistics_per_packet, bytecount_to_unsigned_struct_type[self.nof_bytes_per_statistic]) + format_str = ">{}{}".format(self.nof_statistics_per_packet, bytecount_to_unsigned_struct_type[self.nof_bytes_per_statistic]) + + return numpy.array(struct.unpack(format_str, self.packet[self.header_size:self.header_size + struct.calcsize(format_str)])) + +class XSTPacket(StatisticsPacket): + """ + Models an XST statistics UDP packet from SDP. + + The following fields are exposed as properties & functions. + + + subband_index: subband number for which this packet contains statistics. + baseline: antenna pair for which this packet contains statistics. + """ + + def __init__(self, packet): + super().__init__(packet) + + # We only parse XST packets + if self.marker != 'X': + raise Exception("Payload of XST requested of a non-XST packet. Actual packet marker is '{}', but must be 'X'.".format(self.marker)) + + def unpack_data_id(self): + super().unpack_data_id() + + self.subband_index = get_bit_value(self.data_id, 16, 24) + self.baseline = (get_bit_value(self.data_id, 8, 15), get_bit_value(self.data_id, 0, 7)) + + def header(self): + header = super().header() + + header["data_id"]["subband_index"] = self.subband_index + header["data_id"]["baseline"] = self.baseline + + return header + +class BSTPacket(StatisticsPacket): + """ + Models an BST statistics UDP packet from SDP. + + The following fields are exposed as properties & functions. + + beamlet_index: the number of the beamlet for which this packet holds statistics. + """ + + def __init__(self, packet): + super().__init__(packet) + + # We only parse BST packets + if self.marker != 'B': + raise Exception("Payload of BST requested of a non-BST packet. Actual packet marker is '{}', but must be 'B'.".format(self.marker)) + + def unpack_data_id(self): + super().unpack_data_id() + + self.beamlet_index = get_bit_value(self.data_id, 0, 15) + + def header(self): + header = super().header() - return numpy.array(unpack(format_str, self.packet[32:32+calcsize(format_str)])) + header["data_id"]["beamlet_index"] = self.beamlet_index + return header if __name__ == "__main__": # parse one packet from stdin @@ -211,8 +316,8 @@ if __name__ == "__main__": # read all of stdin, even though we only parse the first packet. we're too lazy to intelligently decide when # the packet is complete and can stop reading. data = sys.stdin.buffer.read() - packet = StatisticsPacket(data) + packet = SSTPacket(data) # print header & payload pprint.pprint(packet.header()) - pprint.pprint(packet.payload_sst) + pprint.pprint(packet.payload)