Index: PDW.py
===================================================================
--- PDW.py	(revision 1e781baa9d163260df25c9e14575de1efe80daeb)
+++ PDW.py	(revision 1e781baa9d163260df25c9e14575de1efe80daeb)
@@ -0,0 +1,228 @@
+import struct
+import math
+import numpy as np
+
+# TODO: Use BitArray module in future versions
+
+
+class PDW():
+    """
+    I store information from a single ppdw data block.
+
+    .. automethod:: __init__
+
+    """
+
+    @classmethod
+    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
+        :rtype: PDW
+
+        """
+
+        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
+
+    def __str__(self):
+        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"
+
+        return output
+
+
+if __name__ == '__main__':
+    pass
