AmmosAudioDataHeader (class in ammosreader.AmmosAudioDataHeader)

from_bytes() (ammosreader.AmmosAudioDataHeader.AmmosAudioDataHeader class method)

mapping (ammosreader.AmmosAudioDataHeader.AmmosAudioDemodType property)

GLOBAL_HEADER_SIZE (ammosreader.AmmosAudioReader.AmmosAudioReader attribute)

add_tag() (ammosreader.AmmosAudioReader.AmmosAudioReader method)

read_all_frames_left() (ammosreader.AmmosAudioReader.AmmosAudioReader method)

read_next_single_frame() (ammosreader.AmmosAudioReader.AmmosAudioReader method)

rewind_to_start() (ammosreader.AmmosAudioReader.AmmosAudioReader method)

AmmosAudioSocketReader (class in ammosreader.AmmosAudioSocketReader)

frequencies() (ammosreader.AmmosContainer.AmmosContainer method)

AmmosExtendedAudioDataHeader (class in ammosreader.AmmosExtendedAudioDataHeader)

from_bytes() (ammosreader.AmmosExtendedAudioDataHeader.AmmosExtendedAudioDataHeader class method)

AmmosExtendedIFDataHeader (class in ammosreader.AmmosExtendedIFDataHeader)

from_bytes() (ammosreader.AmmosExtendedIFDataHeader.AmmosExtendedIFDataHeader class method)

AmmosGlobalFrameBody (class in ammosreader.AmmosGlobalFrameBody)

data_bytes_only() (ammosreader.AmmosGlobalFrameBody.AmmosGlobalFrameBody method)

AmmosGlobalFrameHeader (class in ammosreader.AmmosGlobalFrameHeader)

MAGIC_WORD (ammosreader.AmmosGlobalFrameHeader.AmmosGlobalFrameHeader attribute)

from_bytes() (ammosreader.AmmosGlobalFrameHeader.AmmosGlobalFrameHeader class method)

size() (ammosreader.AmmosGlobalFrameHeader.AmmosGlobalFrameHeader method)

from_bytes() (ammosreader.AmmosIFDataHeader.AmmosIFDataHeader class method)

header_size() (ammosreader.AmmosIFDataHeader.AmmosIFDataHeader method)

read_all_frames_left() (ammosreader.AmmosIFReader.AmmosIFReader method)

read_next_global_frame_body() (ammosreader.AmmosIFReader.AmmosIFReader method)

read_next_global_frame_header() (ammosreader.AmmosIFReader.AmmosIFReader method)

read_next_if_data_blocks() (ammosreader.AmmosIFReader.AmmosIFReader method)

read_next_single_frame() (ammosreader.AmmosIFReader.AmmosIFReader method)

rewind_to_start() (ammosreader.AmmosIFReader.AmmosIFReader method)

size_correct() (ammosreader.AmmosSingleFrame.AmmosSingleFrame method)

import struct
import numpy

class AmmosAudioDemodType():

    @classmethod
    @property
    def mapping(cls):
        return {0: 'FM', 1: 'AM', 5: 'ISB', 6: 'CW',
                7: 'USB', 8: 'LSB', 256: 'DIGITAL',
                0xFFFFFFFF: 'UNKNOWN'}

    def __init__(self, demod_type):
        self.demod_type = demod_type

    def __str__(self):
        return AmmosAudioDemodType.mapping[self.demod_type]

class AmmosAudioDataHeader():

    def from_bytes(cls, bytes):
        elements = struct.unpack('<IIQIIIII', bytes)
        sample_rate = elements[0]
        status = elements[1]
        frequency = elements[2]
        demod_bandwidth = elements[3]
        demod_type = elements[4]
        sample_count = elements[5]
        channel_count = elements[6]
        sample_size = elements[7]
        return AmmosAudioDataHeader(sample_rate, status, frequency, demod_bandwidth, demod_type,
                                    sample_count, channel_count, sample_size)

    def __init__(self, sample_rate, status, frequency, demod_bandwidth, demod_type, sample_count, channel_count, sample_size):
        self.sample_rate = sample_rate
        self.status = status
        self.frequency = frequency
        self.demod_bandwidth = demod_bandwidth
        self.demod_type = AmmosAudioDemodType(demod_type)
        self.sample_count = sample_count
        self.channel_count = channel_count
        self.sample_size = sample_size

        return ("\nAmmosAudioDataHeader\n" +
                "Sample rate:" + str(self.sample_rate) + "\n" +
                "Status:" + str(self.status) + "\n" +
                "Frequency:" + str(self.frequency) + "\n" +
                "Demodulation bandwidth:" + str(self.demod_bandwidth) + "\n" +
                "Demodulation type:" + str(self.demod_type) + "\n" +
                "Sample count:" + str(self.sample_count) + "\n" +
                "Channel count:" + str(self.channel_count) + "\n" +
                "Sample size:" + str(self.sample_size) + "\n")

"""I parse an R&S AMMOS recording."""

import os

from .AmmosGlobalFrameBody import AmmosGlobalFrameBody
from .AmmosAudioDataHeader import AmmosAudioDataHeader
from .AmmosExtendedAudioDataHeader import AmmosExtendedAudioDataHeader
from .AmmosGlobalFrameHeader import AmmosGlobalFrameHeader
from .AmmosSingleFrame import AmmosSingleFrame
from .AmmosContainer import AmmosContainer

class AmmosAudioReader():
    """I read the audio data embedded in an R&S AMMOS recording."""

    GLOBAL_HEADER_SIZE = 24  # 8 words
    STANDARD_AUDIO_DATA_HEADER_SIZE = 36  # 9 words
    EXTENDED_AUDIO_DATA_HEADER_SIZE = 44  # 11 words

    def __init__(self, file_name):
        """
        I return an instance of AmmosAudioReader initialized with a given file name.

        :param file_name: the file to read from
        :type file_name: str
        """
        self.file_name = file_name
        self.file = open(self.file_name, "rb")
        self.file_size = os.path.getsize(self.file_name)

        self.container = AmmosContainer(self.file_name, [])

        self.tags = {}

    def rewind_to_start(self):
        """I set the file pointer to the beginning of the file for the next operation."""
        self.file.seek(0)

    def add_tag(self, tag):
        """
        I add a tag to my tag list.

        :param tag: The tag to add to my tag list
        :type tag: dict
        """
        self.tags[tag.key] = tag.value

    def read_all_frames_left(self):
        """
        I read all remaining frames into my container until end of file is reached.

        :return: a container containing all frames read
        :rtype: AmmosContainer
        """
        frames_read = 0
        while True:
            print("Reading single frame", frames_read, '...')
            current_frame = self.read_next_single_frame()
            if current_frame is not None:
                frames_read += 1
                self.container.add_frame(current_frame)
                if frames_read % 10000 == 0:
                    print("#", end="")
            else:
                print("Frame:", frames_read+1, " incomplete")
                break

        print(len(self.container.global_frames), "frames read")
        return self.container

    def read_next_global_frame_header(self):
        """
        I return the next global frame header read from current position in file.

        :return: the next global frame header or None if incomplete
        :rtype: AmmosGlobalFrameHeader
        """
        bytes = self.file.read(AmmosAudioReader.GLOBAL_HEADER_SIZE)
        print("Reading next global frame header")
        if ((not bytes) or (len(bytes) < AmmosAudioReader.GLOBAL_HEADER_SIZE)):
            print("Can not read all", AmmosAudioReader.GLOBAL_HEADER_SIZE, "bytes of global frame header")
            return None

        # FIXME: Catch exceptions and add some asserts
        current_global_frame_header = AmmosGlobalFrameHeader.from_bytes(bytes)
        # print("Current global frame header", current_global_frame_header)
        return current_global_frame_header

    def read_next_global_frame_body_data_header(self):
        """
        I return the next global frame body data header from current position in file.

        :param data_header_size: the number of bytes to read
        :type data_header_size: int
        :return: the next Ammos Audio Data header or None if incomplete
        :rtype: AmmosAudioDataHeader
        """
        bytes = self.file.read(AmmosAudioReader.STANDARD_AUDIO_DATA_HEADER_SIZE)

        # print("\nReading global frame body standard data header\n")
        if ((not bytes) or (len(bytes) < AmmosAudioReader.STANDARD_AUDIO_DATA_HEADER_SIZE)):
            print("Can not read all", AmmosAudioReader.STANDARD_AUDIO_DATA_HEADER_SIZE,
                  "bytes of global frame body data header")
        return AmmosAudioDataHeader.from_bytes(bytes)

    def read_next_global_frame_body_extended_data_header(self):
        """
        I return the next global frame body extended data header from current position in file.

        :return: the next Ammos Audio Extended Data header or None if incomplete
        :rtype: AmmosExtendedAudioDataHeader
        """
        bytes = self.file.read(AmmosAudioReader.EXTENDED_AUDIO_DATA_HEADER_SIZE)

        if ((not bytes) or (len(bytes) < AmmosAudioReader.EXTENDED_AUDIO_DATA_HEADER_SIZE)):
            print("Can not read all ", AmmosAudioReader.EXTENDED_AUDIO_DATA_HEADER_SIZE,
                  " bytes of global frame extended data header")
        return AmmosExtendedAudioDataHeader.from_bytes(bytes)

    def read_next_audio_data_body(self, sample_count, channel_count, sample_size):
        """
        I return the next audio data read from current position in file.

        :param sample_count: the number of samples per channel inside data body
        :type sample_count: int

        :param channel_count: number of channels (e.g. mono, stereo or even more)
        :type channel_count: int

        :param sample_size: sample size in bytes (1, 2 or 4 bytes)
        :type sample_size: int

        :return: the next audio data or None if incomplete
        :rtype: bytes
        """
        # FIXME: Describe the parameters better

        total = sample_count*channel_count*sample_size

        byte_string = self.file.read(total)

        if len(byte_string) != total:
            print("Can not read all", total, "bytes of data body")
        print([hex(c) for c in byte_string])
        return byte_string

    def read_next_global_frame_body(self, global_frame_header):

        audio_data_header = None

        if global_frame_header.data_header_length == AmmosAudioReader.STANDARD_AUDIO_DATA_HEADER_SIZE:
            print("Read standard data header")
            audio_data_header = self.read_next_global_frame_body_data_header()

        if global_frame_header.data_header_length == AmmosAudioReader.EXTENDED_AUDIO_DATA_HEADER_SIZE:
            print("Read extended data header")
            audio_data_header = self.read_next_global_frame_body_extended_data_header()

        if audio_data_header is None:
            print("Data header missing or format unknown")

        audio_data_body = self.read_next_audio_data_body(audio_data_header.sample_count,
                                                         audio_data_header.channel_count,
                                                         audio_data_header.sample_size)

        if audio_data_body is None:
            print("Data body missing")

        return AmmosGlobalFrameBody(audio_data_header, audio_data_body)

    def read_next_single_frame(self):

        global_frame_header = self.read_next_global_frame_header()

        print(global_frame_header)

        if global_frame_header is None:
            print("Global frame header missing")

        if global_frame_header.data_header_length is None:
            print("Data header length empty")

        if global_frame_header.frame_type == 256:
            print("Audio Datastream found")
            global_frame_body = self.read_next_global_frame_body(global_frame_header)
            if global_frame_body is None:
                return None
        else:
            print("Unsupported frame type", global_frame_header.frame_type, "found")

        ammos_single_frame = AmmosSingleFrame(global_frame_header, global_frame_body)
        return ammos_single_frame

import socket
from collections import deque

import numpy as np

class AmmosAudioSocketReader:
    def __init__(self, socket:socket.socket):
        """
        Initializes the AmmosAudioSocketReader

        Args:
            socket (socket.socket): socket to read from
        """
        
        self.__socket = socket

        #buffer for reading socket bytewise und check for the magic word
        self.__magic_word_buffer = deque(maxlen=4)

        #input socket to read from

    def __read_next_audio_data_body(self, sample_count:int, channel_count:int, sample_size:int) -> bytearray:
        """
        reads the next audio data body

        Args:
            sample_count (int): amount of samples
            channel_count (int): amount of channels
            sample_size (int): size of a sample in bytes

        Returns:
            bytearray: contains the audio data
        """

        byte_array = []

        while len(b''.join(byte_array)) < total:
            byte_array.append(self.__socket.recv(total - len(b''.join(byte_array))))

        if len(b''.join(byte_array)) != total:
        return b''.join(byte_array)

    def __audio_data_body_to_numpy(self, audio_data_body:bytearray) -> np.ndarray:
        """
        converts the audio data body to a numpy array

        Args:
            audio_data_body (bytearray): audio data from audio data body

        Returns:
            np.ndarray: audio data as numpy array
        """

        return np.frombuffer(audio_data_body, dtype=np.int16)

    def read_next_frame(self) -> tuple[bytearray, int]:
        """
        reads the next ammos audio frame

        Returns:
            tuple[bytearray, int]: contains the audio data and the sample rate
        """
        #read loop
        byte = self.__socket.recv(1)

        while byte:
            #
            self.__magic_word_buffer.append(byte)
            byte_array = b''.join(self.__magic_word_buffer)

            if byte_array.hex() == '726574fb':
                #print(byte_array.hex())

                ammos_global_header_buffer = list(self.__magic_word_buffer)
                while len(b''.join(ammos_global_header_buffer)) < 24:
                    ammos_global_header_buffer.append(self.__socket.recv(24 - len(b''.join(ammos_global_header_buffer))))
                    
                ammos_global_header = AmmosGlobalFrameHeader.from_bytes(b''.join(ammos_global_header_buffer))
                print(ammos_global_header)

                if ammos_global_header.data_header_length == 44 and ammos_global_header.frame_type == 256:
                    byte_array_header = []
                    while len(b''.join(byte_array_header)) < 44:
                        byte_array_header.append(self.__socket.recv(44 - len(b''.join(byte_array_header))))

                    ammos_extended_audio_data_header = AmmosExtendedAudioDataHeader.from_bytes(b''.join(byte_array_header))
                    print(ammos_extended_audio_data_header.sample_count, ammos_extended_audio_data_header.channel_count, ammos_extended_audio_data_header.sample_size)
                    audio_body = self.__read_next_audio_data_body(ammos_extended_audio_data_header.sample_count, 
                                                                  ammos_extended_audio_data_header.channel_count, 
                                                                  ammos_extended_audio_data_header.sample_size)

                    audio_array = self.__audio_data_body_to_numpy(audio_body)
                    print(len(audio_array), len(audio_array)/ammos_extended_audio_data_header.sample_rate)

                    return [audio_array, ammos_extended_audio_data_header.sample_rate]

                elif ammos_global_header.data_header_length == 36 and ammos_global_header.frame_type == 256:
                    while len(b''.join(byte_array_header)) < 36:
                        byte_array_header.append(self.__socket.recv(36 - len(b''.join(byte_array_header))))

                    ammos_audio_data_header = AmmosAudioDataHeader.from_bytes(b''.join(byte_array_header))
                    print(ammos_audio_data_header.sample_count, ammos_audio_data_header.channel_count, ammos_audio_data_header.sample_size)
                    audio_body = self.__read_next_audio_data_body(ammos_audio_data_header.sample_count, 
                                                                  ammos_audio_data_header.channel_count, 
                                                                  ammos_audio_data_header.sample_size)

                    print(len(audio_array), len(audio_array)/ammos_audio_data_header.sample_rate)

                    return [audio_array, ammos_audio_data_header.sample_rate]

            byte = self.__socket.recv(1)

        return None

from datetime import datetime

class AmmosContainer():

    def __init__(self, name, frames):
        self.name = name
        self.global_frames = frames
        self.tags = []

        self.tags.append(tag)

    def add_frame(self, frame):
        self.global_frames.append(frame)

    def size(self):
        len(self.global_frames)

    def frequencies(self):
        return set(list(filter(lambda frame: frame.global_frame_body.data_header.frequency, self.global_frames)))

        start_time = self.global_frames[0].global_frame_body.data_header.timestamp
        end_time = self.global_frames[-1].global_frame_body.data_header.timestamp

        frq = str(self.global_frames[0].global_frame_body.data_header.frequency)

        return ("Start time: " + str(datetime.utcfromtimestamp(start_time/1000000)) +
                "\nEnd time  : " + str(datetime.utcfromtimestamp(end_time/1000000)) + "\nFrequencies: " + frq)

class AmmosExtendedAudioDataHeader():

        standard_header = AmmosAudioDataHeader.from_bytes(bytes[0:36])
        extended_header_elements = struct.unpack('Q', bytes[36:])
        timestamp = extended_header_elements[0]
        sample_rate = standard_header.sample_rate
        status = standard_header.status
        frequency = standard_header.frequency
        demod_bandwidth = standard_header.demod_bandwidth
        demod_type = standard_header.demod_type
        sample_count = standard_header.sample_count
        channel_count = standard_header.channel_count
        sample_size = standard_header.sample_size
        return AmmosExtendedAudioDataHeader(sample_rate, status, frequency, demod_bandwidth, demod_type,
                                            sample_count, channel_count, sample_size, timestamp)

    def __init__(self, sample_rate, status, frequency, demod_bandwidth, demod_type,
                 sample_count, channel_count, sample_size, timestamp):
        self.timestamp = timestamp

from .AmmosIFDataHeader import AmmosIFDataHeader

class AmmosExtendedIFDataHeader():

        standard_header = AmmosIFDataHeader.from_bytes(bytes[0:56])
        extended_header_elements = struct.unpack('QQI', bytes[56:76])
        block_count = standard_header.block_count
        block_length = standard_header.block_length
        timestamp = standard_header.timestamp
        source_id = standard_header.source_id
        source_state = standard_header.source_state
        bandwidth = standard_header.bandwidth
        interpolation = standard_header.interpolation
        decimation = standard_header.decimation
        voltage_ref = standard_header.voltage_ref
        stream_start = np.datetime64(int(extended_header_elements[0]), 'ns')
        sample_counter = extended_header_elements[1]
        antenna_correction = extended_header_elements[2]
        size = len(bytes)
        return AmmosExtendedIFDataHeader(size, block_count, block_length, timestamp, status, source_id,
                                         source_state, frequency, bandwidth, sample_rate, interpolation,
                                         decimation, voltage_ref, stream_start, sample_counter,
                                         antenna_correction)

    def __init__(self, size, block_count, block_length, timestamp, status, source_id, source_state, frequency,
                 bandwidth, sample_rate, interpolation, decimation, voltage_ref, stream_start, sample_counter,
                 antenna_correction):

        self.size = size
        self.block_count = block_count
        self.block_length = block_length
        self.source_id = source_id
        self.source_state = source_state
        self.bandwidth = bandwidth
        self.interpolation = interpolation
        self.decimation = decimation
        self.voltage_ref = voltage_ref
        self.stream_start = stream_start
        self.sample_counter = sample_counter
        self.antenna_correction = antenna_correction

        output = (
            "\nGlobal frame body data header\n" +
            "-----------------------------\n" +
            "Block count:" + str(self.block_count) + "\n" +
            "Block length:" + str(self.block_length) + "\n" +
            "Time stamp:" + str(self.timestamp) + "\n" +
            "Frequency:" + str(self.frequency) + "\n" +
            "Bandwidth:" + str(self.bandwidth) + "\n" +
            "Sample rate:" + str(self.sample_rate) + "\n" +
            "Stream start:" + str(self.stream_start) + "\n" +
            "Sample counter:" + str(self.sample_counter) + "\n" +
            "Antenna correction:" + str(self.antenna_correction) + "\n"
        )

        return output

class AmmosGlobalFrameBody():

    def __init__(self, data_header, data_body):
        self.data_header = data_header
        self.data_body = data_body

    def data_bytes_only(self):

        byte_string = ""

        for each_block in self.data_body:
            if not each_block:
                print("Block is nil")

            byte_string += each_block.if_data

class AmmosGlobalFrameHeader():

    MAGIC_WORD = "726574fb"

        elements = struct.unpack('4s4s4s4s4s4s', bytes)

        magic_word = elements[0].hex()

        frame_length = (int.from_bytes(elements[1], byteorder='little')*4)

        running_frame_number = (int.from_bytes(elements[2], byteorder='little'))

        frame_type = (int.from_bytes(elements[3], byteorder='little'))

        data_header_length = 4 * int.from_bytes(elements[4], byteorder='little')

        reserved = elements[5]

        return AmmosGlobalFrameHeader(magic_word, frame_length, running_frame_number,
                                      frame_type, data_header_length, reserved)

    def __init__(self, magic_word, frame_length, running_frame_number, frame_type, data_header_length, reserved):

        if magic_word != self.MAGIC_WORD:
            print("Wrong magic word")
            self.magic_word = magic_word
        else:
            self.frame_length = frame_length
            self.running_frame_number = running_frame_number
            self.frame_type = frame_type
            self.data_header_length = data_header_length
            self.reserved = reserved

        output = ("Global frame header info\n" +
                  "------------------------\n" +
                  "Magic word:" + str(self.magic_word) + "\n" +
                  "Frame length:" + str(self.frame_length) + "\n" +
                  "Running frame:" + str(self.running_frame_number) + "\n" +
                  "Frame Type:" + str(self.frame_type) + "\n" +
                  "Data header length:" + str(self.data_header_length) + "\n")

        return 24

class AmmosIFDataBlock():

    def __init__(self, if_datablock_header, if_data):
        self.if_datablock_header = if_datablock_header
        self.if_data = if_data

class AmmosIFDataHeader():

        elements = struct.unpack('<IIQIIIQIIIIi', bytes)
        block_count = elements[0]
        block_length = int(elements[1])*4
        timestamp = np.datetime64(int(elements[2])*1000, 'ns')
        status = elements[3]
        source_id = elements[4]
        source_state = elements[5]
        frequency = elements[6]
        bandwidth = elements[7]
        sample_rate = elements[8]
        interpolation = elements[9]
        decimation = elements[10]
        voltage_ref = elements[11]

        return AmmosIFDataHeader(block_count, block_length, timestamp, status, source_id,
                                 source_state, frequency, bandwidth, sample_rate,
                                 interpolation, decimation, voltage_ref)

    def __init__(self, block_count, block_length, timestamp, status, source_id, source_state, frequency,
                 bandwidth, sample_rate, interpolation, decimation, voltage_ref):

    def header_size(self):
        return 56

    def __str_(self):
        output = ("\nGlobal frame body data header\n" +
                  "-----------------------------\n" +
                  "Block count:" + str(self.block_count) + "\n" +
                  "Block length:" + str(self.block_length) + "\n" +
                  "Time stamp:" + str(self.timestamp) + "\n" +
                  "Frequency:" + str(self.frequency) + "\n" +
                  "Bandwidth:" + str(self.bandwidth) + "\n" +
                  "Sample rate:" + str(self.sample_rate) + "\n")

import math
import os

from .AmmosExtendedIFDataHeader import AmmosExtendedIFDataHeader
from .AmmosIFDataBlock import AmmosIFDataBlock

class AmmosIFReader():

            else:
                break

        bytes = self.file.read(24)
        if ((not bytes) or (len(bytes) < 24)):
            print("Can not read all 24 bytes of global frame header")

        return AmmosGlobalFrameHeader.from_bytes(bytes)

        bytes = self.file.read(56)

        if ((not bytes) or (len(bytes) < 56)):
            print("Can not read all 56 bytes of global frame body data header")

        data_header = AmmosIFDataHeader.from_bytes(bytes)
        # print("Data header", data_header)
        return data_header

        bytes = self.file.read(76)
        # print("\nReading global frame body extended data header\n")

        if ((not bytes) or (len(bytes) < 76)):
            print("Can not read all ", 76, "bytes of global frame extended data header")
        extended_data_header = AmmosExtendedIFDataHeader.from_bytes(bytes)
        # print("Extended data header", extended_data_header)
        return extended_data_header

    def read_next_if_data_blocks(self, n, length):

        data_blocks = []

        block_length = 4 + length

        total = n*block_length

        byte_string = self.file.read(block_length)

        for i in range(0, n):
            result = byte_string[i*block_length:(i*block_length+block_length)]
            data_blocks.append(AmmosIFDataBlock(result[0:4], result[4:]))

        return data_blocks

        if_data_header = None

        if global_frame_header.data_header_length == 56:
            if_data_header = self.read_next_global_frame_body_data_header()
        else:
            if_data_header = self.read_next_global_frame_body_extended_data_header()

        if if_data_header is None:
            print("Data header missing")

        if_data_body = self.read_next_if_data_blocks(if_data_header.block_count, if_data_header.block_length)

        if if_data_body is None:

        return AmmosGlobalFrameBody(if_data_header, if_data_body)

        print("\nReading next global frame header\n", global_frame_header)
        # print("File pointer", self.file.tell())

        if global_frame_header.frame_type == 2:

        else:

        return AmmosSingleFrame(global_frame_header, global_frame_body)

class AmmosSingleFrame():

    def __init__(self, global_frame_header, global_frame_body):
        self.global_frame_header = global_frame_header
        self.global_frame_body = global_frame_body

    def data(self):
        return self.global_frame_body.data_bytes_only()

    def size_correct(self):
        return (self.global_frame_header.frame_length == (24 + self.global_frame_header.data_header_length +
                                                          (self.global_frame_body.data_header.block_count *
                                                           (self.global_frame_body.data_header.block_length + 4))))
    # FIXME: Use str method instead

        output = (
            "Global frame header\n" +
            "-------------------\n" +
            "Frame tyoe:" + str(self.global_frame_header.frame_type) + "\n" +
            "Frame count:" + str(self.global_frame_header.running_frame_number) + "\n" +
            "Data header length:" + str(self.global_frame_header.data_header_length) + " bytes\n" +
            "Frame length:" + str(self.global_frame_header.frame_length) + " bytes\n"
        )

# TODO: Use BitArray module in future versions

class PDW():
    """
    I store information from a single ppdw data block.

    .. automethod:: __init__

    """

    def from_bytes(cls, byte_string):

        """

        I create an instance of class PDW from data body (8 * 32 bits)

        :param byte_string: a byte string containing a single data body read from a ppdw file
        :type byte_string: byte string

        :return: an instance of class PDW with attributes set according to the data of a data body
        :rtype: PDW

        """

        assert(len(byte_string) == 32)

        parts = struct.unpack('Q4s4s4s4s4s4s', byte_string)
        nanoseconds = (parts[0])
        time_of_arrival = np.datetime64(nanoseconds, 'ns')  #datetime.datetime.utcfromtimestamp(seconds / 1000000000)

        third_entry = bin(int.from_bytes(parts[1], byteorder='little'))
        padding = 32-len(str(third_entry)[2:])
        third_entry_bit_string = "0" * padding + str(third_entry)[2:]
        pdw_format_identifier = int(third_entry_bit_string[0:6], 2)
        center_frequency = int(third_entry_bit_string[5:32], 2)

        fourth_entry = bin(int.from_bytes(parts[2], byteorder='little'))
        padding = 32-len(str(fourth_entry)[2:])
        fourth_entry_bit_string = "0" * padding + str(fourth_entry)[2:]
        is_valid = bool(int(fourth_entry_bit_string[0]))
        is_pulse = bool(int(fourth_entry_bit_string[1]))
        level_unit = int(fourth_entry_bit_string[2])
        signal_start_missing = bool(int(fourth_entry_bit_string[3]))
        signal_end_missing = bool(int(fourth_entry_bit_string[4]))
        pulse_width = int(fourth_entry_bit_string[7:33], 2)

        fifth_entry = bin(int.from_bytes(parts[3], byteorder='little'))
        padding = 32-len(str(fifth_entry)[2:])
        fifth_entry_bit_string = "0" * padding + str(fifth_entry)[2:]
        frequency_shift_or_bandwidth = int(fifth_entry_bit_string[0:20], 2)
        # FIXME: You have to scale me to the range from -200.0 to 200.0 in 0.1 steps
        pulse_level_or_pulse_field_strength = math.ceil(int(fifth_entry_bit_string[20:32], 2)) / 10

        sixth_entry = bin(int.from_bytes(parts[4], byteorder='little'))
        padding = 32-len(str(sixth_entry)[2:])
        sixth_entry_bit_string = "0" * padding + str(sixth_entry)[2:]
        region_of_interest = bool(int(sixth_entry_bit_string[0]))
        # FIXME: You have to scale me to a range from 0.0 to 6.2 in steps of 0.1 - 6.3 means unknown
        azimuth_confidence = math.ceil(int(sixth_entry_bit_string[1:7], 2)) / 10
        modulations = {0: 'Unknown', 1: 'Unmodulated', 2: 'FM', 3: 'LFM', 4: 'PSK-2', 5: 'PSK-3', 6: 'PSK-4',
                       7: 'PSK-m', 8: 'NLFM', 9: 'SFM', 10: 'TFM', 11: 'Pulse too short'}
        modulation = modulations[int(sixth_entry_bit_string[7:12], 2)]
        sector = int(sixth_entry_bit_string[28:32], 2)

        seventh_entry = bin(int.from_bytes(parts[5], byteorder='little'))
        padding = 32-len(str(seventh_entry)[2:])
        seventh_entry_bit_string = "0" * padding + str(seventh_entry)[2:]
        polarities = {0: 'Horizontal/Unknown', 1: 'Vertical', 2: 'Counter clockwise', 3: 'Clockwise'}
        polarity = polarities[int(seventh_entry_bit_string[0:2], 2)]
        df_quality = int(seventh_entry_bit_string[2:9], 2)
        # FIXME: You have to scale me from -90 to 90 in 0.1 degree steps
        elevation = int(seventh_entry_bit_string[9:20], 2)
        # FIXME: You have to check me for a range from 0.0 to 359.9 in steps of 0.1
        azimuth = 0.1 * (int(seventh_entry_bit_string[20:32], 2))

        eighth_entry = bin(int.from_bytes(parts[5], byteorder='little'))
        padding = 32-len(str(eighth_entry)[2:])
        eighth_entry_bit_string = "0" * padding + str(eighth_entry)[2:]
        channel = int(eighth_entry_bit_string[0:4], 2)

        return PDW(time_of_arrival, pdw_format_identifier, center_frequency, is_valid, is_pulse, level_unit,
                   signal_start_missing, signal_end_missing, pulse_width, frequency_shift_or_bandwidth,
                   pulse_level_or_pulse_field_strength, region_of_interest, azimuth_confidence, modulation,
                   sector, polarity, df_quality, elevation, azimuth, channel)

    def __init__(self, time_of_arrival, pdw_format_identifier, center_frequency, is_valid, is_pulse,
                 level_unit, signal_start_missing, signal_end_missing, pulse_width, frequency_shift_or_bandwidth,
                 pulse_level_or_pulse_field_strength, region_of_interest, azimuth_confidence, modulation,
                 sector, polarity, df_quality, elevation, azimuth, channel):

        """

        :param time_of_arrival: nanoseconds since 1970-01-01 00:00:00
        :type time_of_arrival: Integer
        :param pdw_format: format code
        :type pdw_format: Integer
        :param center_frequency: center frequency in KHz
        :type center_frequency: Integer
        :param is_valid: flag to mark if pdw data body is valid
        :type is_valid: Boolean
        :param is_pulse: flag to mark if pdw data body contains a pulse or a continuous wave signal
        :type is_pulse: Boolean
        :param level_unit: 0 means dBµV - 1 means dBµV/m
        :type level_unit: Integer
        :param signal_start_missing: signal started before time of arrival
        :type signal_start_missing: Boolean
        :param signal_end_missing: signal stops after time of arrival
        :type signal_end_missing: Boolean
        :param pulse_width: pulse width in nanoseconds - Zero if no valid pulse detected
        :type pulse_width: Integer
        :param frequency_shift_or_bandwidth: Value in KHz - Value set to 1048575 means Unknown
        :type frequency_shift_or_bandwidth: Integer
        :param pulse_level_or_pulse_field_strength: Pulse level or Pulse Field Strength depending on level_unit \
         (-200.0...200.0) in 0.1 steps / minus 204.8 means no valid level detected
        :type pulse_level_or_pulse_field_strength: Float
        :param region_of_interest: Marks if signal is from region of interest
        :type region_of_interest: Boolean
        :param azimuth_confidence: degree in steps of 0.1 (0.0-6.2) / 6.3 means confidence unknown
        :type azimuth_confidence: Float
        :param modulation: type of modulation (e.g. PSK-2, PSK-4, FM etc.)
        :type modulation: String
        :param sector: reference antenna sector (0-15)
        :type sector: Integer
        :param polarity: Horizontal, Vertical, Clockwise, Counter clockwise
        :type polarity: String
        :param df_quality: Direction finding quality in percent (0-100) - Zero means unknown
        :type df_quality: Integer
        :param elevation: elevation of incoming signal (from -90 to 90 degree) in steps of 0.1 degree \
        minus 102.4 means unknown
        :type elevation: Float
        :param azimuth: azimuth of incoming signal (from 0 to 359.9 degree) in steps of 0.1 degree \
        plus 409.5 means unknown
        :type azimuth: Float
        :param channel: detecting channel (0-16) - Zero means unknown
        :type channel: Integer
        :return: An instance of class PDW with attributes set according to the data of a data body

        """

        self.time_of_arrival = time_of_arrival
        self.pdw_format_identifier = pdw_format_identifier
        self.center_frequency = center_frequency
        self.is_valid = is_valid
        self.is_pulse = is_pulse
        self.level_unit = level_unit
        self.signal_start_missing = signal_start_missing
        self.signal_end_missing = signal_end_missing
        self.pulse_width = pulse_width
        self.frequency_shift_or_bandwidth = frequency_shift_or_bandwidth
        self.pulse_level_or_pulse_field_strength = pulse_level_or_pulse_field_strength
        self.region_of_interest = region_of_interest
        self.azimuth_confidence = azimuth_confidence
        self.modulation = modulation
        self.sector = sector
        self.polarity = polarity
        self.df_quality = df_quality
        self.elevation = elevation
        self.azimuth = azimuth
        self.channel = channel

        output = ("Time of arrival: " + str(self.time_of_arrival) + "\n" +
                  "PDW Format identifier: " + str(self.pdw_format_identifier) + "\n" +
                  "Center frequency: " + str(self.center_frequency) + " KHz\n")

        if self.is_valid:
            output += "Signal: Valid\n"
        else:
            output += "Signal: Invalid\n"

        if self.is_pulse:
            output += "Signal type: Pulse\n"
        else:
            output += "Signal type: Continuous wave\n"

        if self.level_unit == 1:
            output += "Pulse level: " + str(self.pulse_level_or_pulse_field_strength) + " dbµV\n"
        else:
            output += "Pulse field strength: " + str(self.pulse_level_or_pulse_field_strength) + " dbµV/meter\n"

        output += ("Pulse width: " + str(self.pulse_width) + " nanoseconds\n" +
                   "Frequency shift or bandwidth: " + str(self.frequency_shift_or_bandwidth) + " KHz\n")

        if self.region_of_interest:
            output += "Region of interest: Yes\n"
        else:
            output += "Region of interest: No\n"

        if self.azimuth_confidence == 6.3:
            output += "Azimuth confidence: Invalid\n"
        else:
            output += "Azimuth confidence: " + str(self.azimuth_confidence) + " degree\n"

        output += "Modulation: " + str(self.modulation) + "\n"

        if self.sector == 0:
            output += "Sector: Unknown\n"
        else:
            output += "Sector:" + str(self.sector) + "\n"

        output += "Polarity: " + str(self.polarity) + "\n"

        output += "DF quality: " + str(self.df_quality) + " %\n"

        if self.elevation == 1024:
            output += "Elevation: Unknown\n"
        else:
            output += "Elevation: " + str(self.elevation) + " degree\n"

        if self.azimuth == 409.5:
            output += "Azimuth: Unknown\n"
        else:
            output += "Azimuth: " + str(self.azimuth) + " degree\n"

        output += "Channel: " + str(self.channel) + "\n"

if __name__ == '__main__':
    pass

class PPDWContainer():

    """

    I store multiple signals imported from one or more .ppdw files

    """

    def __init__(self, signals):

        self.signals = signals

    pass