| [1e781ba] | 1 | import struct | 
|---|
|  | 2 | import math | 
|---|
|  | 3 | import numpy as np | 
|---|
|  | 4 |  | 
|---|
|  | 5 | # TODO: Use BitArray module in future versions | 
|---|
|  | 6 |  | 
|---|
|  | 7 |  | 
|---|
|  | 8 | class PDW(): | 
|---|
|  | 9 | """ | 
|---|
|  | 10 | I store information from a single ppdw data block. | 
|---|
|  | 11 |  | 
|---|
|  | 12 | .. automethod:: __init__ | 
|---|
|  | 13 |  | 
|---|
|  | 14 | """ | 
|---|
|  | 15 |  | 
|---|
|  | 16 | @classmethod | 
|---|
|  | 17 | def from_bytes(cls, byte_string): | 
|---|
|  | 18 |  | 
|---|
|  | 19 | """ | 
|---|
|  | 20 |  | 
|---|
|  | 21 | I create an instance of class PDW from data body (8 * 32 bits) | 
|---|
|  | 22 |  | 
|---|
|  | 23 | :param byte_string: a byte string containing a single data body read from a ppdw file | 
|---|
|  | 24 | :type byte_string: byte string | 
|---|
|  | 25 |  | 
|---|
|  | 26 | :return: an instance of class PDW with attributes set according to the data of a data body | 
|---|
|  | 27 | :rtype: PDW | 
|---|
|  | 28 |  | 
|---|
|  | 29 | """ | 
|---|
|  | 30 |  | 
|---|
|  | 31 | assert(len(byte_string) == 32) | 
|---|
|  | 32 |  | 
|---|
|  | 33 | parts = struct.unpack('Q4s4s4s4s4s4s', byte_string) | 
|---|
|  | 34 | nanoseconds = (parts[0]) | 
|---|
|  | 35 | time_of_arrival = np.datetime64(nanoseconds, 'ns')  #datetime.datetime.utcfromtimestamp(seconds / 1000000000) | 
|---|
|  | 36 |  | 
|---|
|  | 37 | third_entry = bin(int.from_bytes(parts[1], byteorder='little')) | 
|---|
|  | 38 | padding = 32-len(str(third_entry)[2:]) | 
|---|
|  | 39 | third_entry_bit_string = "0" * padding + str(third_entry)[2:] | 
|---|
|  | 40 | pdw_format_identifier = int(third_entry_bit_string[0:6], 2) | 
|---|
|  | 41 | center_frequency = int(third_entry_bit_string[5:32], 2) | 
|---|
|  | 42 |  | 
|---|
|  | 43 | fourth_entry = bin(int.from_bytes(parts[2], byteorder='little')) | 
|---|
|  | 44 | padding = 32-len(str(fourth_entry)[2:]) | 
|---|
|  | 45 | fourth_entry_bit_string = "0" * padding + str(fourth_entry)[2:] | 
|---|
|  | 46 | is_valid = bool(int(fourth_entry_bit_string[0])) | 
|---|
|  | 47 | is_pulse = bool(int(fourth_entry_bit_string[1])) | 
|---|
|  | 48 | level_unit = int(fourth_entry_bit_string[2]) | 
|---|
|  | 49 | signal_start_missing = bool(int(fourth_entry_bit_string[3])) | 
|---|
|  | 50 | signal_end_missing = bool(int(fourth_entry_bit_string[4])) | 
|---|
|  | 51 | pulse_width = int(fourth_entry_bit_string[7:33], 2) | 
|---|
|  | 52 |  | 
|---|
|  | 53 | fifth_entry = bin(int.from_bytes(parts[3], byteorder='little')) | 
|---|
|  | 54 | padding = 32-len(str(fifth_entry)[2:]) | 
|---|
|  | 55 | fifth_entry_bit_string = "0" * padding + str(fifth_entry)[2:] | 
|---|
|  | 56 | frequency_shift_or_bandwidth = int(fifth_entry_bit_string[0:20], 2) | 
|---|
|  | 57 | # FIXME: You have to scale me to the range from -200.0 to 200.0 in 0.1 steps | 
|---|
|  | 58 | pulse_level_or_pulse_field_strength = math.ceil(int(fifth_entry_bit_string[20:32], 2)) / 10 | 
|---|
|  | 59 |  | 
|---|
|  | 60 | sixth_entry = bin(int.from_bytes(parts[4], byteorder='little')) | 
|---|
|  | 61 | padding = 32-len(str(sixth_entry)[2:]) | 
|---|
|  | 62 | sixth_entry_bit_string = "0" * padding + str(sixth_entry)[2:] | 
|---|
|  | 63 | region_of_interest = bool(int(sixth_entry_bit_string[0])) | 
|---|
|  | 64 | # FIXME: You have to scale me to a range from 0.0 to 6.2 in steps of 0.1 - 6.3 means unknown | 
|---|
|  | 65 | azimuth_confidence = math.ceil(int(sixth_entry_bit_string[1:7], 2)) / 10 | 
|---|
|  | 66 | modulations = {0: 'Unknown', 1: 'Unmodulated', 2: 'FM', 3: 'LFM', 4: 'PSK-2', 5: 'PSK-3', 6: 'PSK-4', | 
|---|
|  | 67 | 7: 'PSK-m', 8: 'NLFM', 9: 'SFM', 10: 'TFM', 11: 'Pulse too short'} | 
|---|
|  | 68 | modulation = modulations[int(sixth_entry_bit_string[7:12], 2)] | 
|---|
|  | 69 | sector = int(sixth_entry_bit_string[28:32], 2) | 
|---|
|  | 70 |  | 
|---|
|  | 71 | seventh_entry = bin(int.from_bytes(parts[5], byteorder='little')) | 
|---|
|  | 72 | padding = 32-len(str(seventh_entry)[2:]) | 
|---|
|  | 73 | seventh_entry_bit_string = "0" * padding + str(seventh_entry)[2:] | 
|---|
|  | 74 | polarities = {0: 'Horizontal/Unknown', 1: 'Vertical', 2: 'Counter clockwise', 3: 'Clockwise'} | 
|---|
|  | 75 | polarity = polarities[int(seventh_entry_bit_string[0:2], 2)] | 
|---|
|  | 76 | df_quality = int(seventh_entry_bit_string[2:9], 2) | 
|---|
|  | 77 | # FIXME: You have to scale me from -90 to 90 in 0.1 degree steps | 
|---|
|  | 78 | elevation = int(seventh_entry_bit_string[9:20], 2) | 
|---|
|  | 79 | # FIXME: You have to check me for a range from 0.0 to 359.9 in steps of 0.1 | 
|---|
|  | 80 | azimuth = 0.1 * (int(seventh_entry_bit_string[20:32], 2)) | 
|---|
|  | 81 |  | 
|---|
|  | 82 | eighth_entry = bin(int.from_bytes(parts[5], byteorder='little')) | 
|---|
|  | 83 | padding = 32-len(str(eighth_entry)[2:]) | 
|---|
|  | 84 | eighth_entry_bit_string = "0" * padding + str(eighth_entry)[2:] | 
|---|
|  | 85 | channel = int(eighth_entry_bit_string[0:4], 2) | 
|---|
|  | 86 |  | 
|---|
|  | 87 | return PDW(time_of_arrival, pdw_format_identifier, center_frequency, is_valid, is_pulse, level_unit, | 
|---|
|  | 88 | signal_start_missing, signal_end_missing, pulse_width, frequency_shift_or_bandwidth, | 
|---|
|  | 89 | pulse_level_or_pulse_field_strength, region_of_interest, azimuth_confidence, modulation, | 
|---|
|  | 90 | sector, polarity, df_quality, elevation, azimuth, channel) | 
|---|
|  | 91 |  | 
|---|
|  | 92 | def __init__(self, time_of_arrival, pdw_format_identifier, center_frequency, is_valid, is_pulse, | 
|---|
|  | 93 | level_unit, signal_start_missing, signal_end_missing, pulse_width, frequency_shift_or_bandwidth, | 
|---|
|  | 94 | pulse_level_or_pulse_field_strength, region_of_interest, azimuth_confidence, modulation, | 
|---|
|  | 95 | sector, polarity, df_quality, elevation, azimuth, channel): | 
|---|
|  | 96 |  | 
|---|
|  | 97 | """ | 
|---|
|  | 98 |  | 
|---|
|  | 99 | :param time_of_arrival: nanoseconds since 1970-01-01 00:00:00 | 
|---|
|  | 100 | :type time_of_arrival: Integer | 
|---|
|  | 101 | :param pdw_format: format code | 
|---|
|  | 102 | :type pdw_format: Integer | 
|---|
|  | 103 | :param center_frequency: center frequency in KHz | 
|---|
|  | 104 | :type center_frequency: Integer | 
|---|
|  | 105 | :param is_valid: flag to mark if pdw data body is valid | 
|---|
|  | 106 | :type is_valid: Boolean | 
|---|
|  | 107 | :param is_pulse: flag to mark if pdw data body contains a pulse or a continuous wave signal | 
|---|
|  | 108 | :type is_pulse: Boolean | 
|---|
|  | 109 | :param level_unit: 0 means dBµV - 1 means dBµV/m | 
|---|
|  | 110 | :type level_unit: Integer | 
|---|
|  | 111 | :param signal_start_missing: signal started before time of arrival | 
|---|
|  | 112 | :type signal_start_missing: Boolean | 
|---|
|  | 113 | :param signal_end_missing: signal stops after time of arrival | 
|---|
|  | 114 | :type signal_end_missing: Boolean | 
|---|
|  | 115 | :param pulse_width: pulse width in nanoseconds - Zero if no valid pulse detected | 
|---|
|  | 116 | :type pulse_width: Integer | 
|---|
|  | 117 | :param frequency_shift_or_bandwidth: Value in KHz - Value set to 1048575 means Unknown | 
|---|
|  | 118 | :type frequency_shift_or_bandwidth: Integer | 
|---|
|  | 119 | :param pulse_level_or_pulse_field_strength: Pulse level or Pulse Field Strength depending on level_unit \ | 
|---|
|  | 120 | (-200.0...200.0) in 0.1 steps / minus 204.8 means no valid level detected | 
|---|
|  | 121 | :type pulse_level_or_pulse_field_strength: Float | 
|---|
|  | 122 | :param region_of_interest: Marks if signal is from region of interest | 
|---|
|  | 123 | :type region_of_interest: Boolean | 
|---|
|  | 124 | :param azimuth_confidence: degree in steps of 0.1 (0.0-6.2) / 6.3 means confidence unknown | 
|---|
|  | 125 | :type azimuth_confidence: Float | 
|---|
|  | 126 | :param modulation: type of modulation (e.g. PSK-2, PSK-4, FM etc.) | 
|---|
|  | 127 | :type modulation: String | 
|---|
|  | 128 | :param sector: reference antenna sector (0-15) | 
|---|
|  | 129 | :type sector: Integer | 
|---|
|  | 130 | :param polarity: Horizontal, Vertical, Clockwise, Counter clockwise | 
|---|
|  | 131 | :type polarity: String | 
|---|
|  | 132 | :param df_quality: Direction finding quality in percent (0-100) - Zero means unknown | 
|---|
|  | 133 | :type df_quality: Integer | 
|---|
|  | 134 | :param elevation: elevation of incoming signal (from -90 to 90 degree) in steps of 0.1 degree \ | 
|---|
|  | 135 | minus 102.4 means unknown | 
|---|
|  | 136 | :type elevation: Float | 
|---|
|  | 137 | :param azimuth: azimuth of incoming signal (from 0 to 359.9 degree) in steps of 0.1 degree \ | 
|---|
|  | 138 | plus 409.5 means unknown | 
|---|
|  | 139 | :type azimuth: Float | 
|---|
|  | 140 | :param channel: detecting channel (0-16) - Zero means unknown | 
|---|
|  | 141 | :type channel: Integer | 
|---|
|  | 142 | :return: An instance of class PDW with attributes set according to the data of a data body | 
|---|
|  | 143 | :rtype: PDW | 
|---|
|  | 144 |  | 
|---|
|  | 145 | """ | 
|---|
|  | 146 |  | 
|---|
|  | 147 | self.time_of_arrival = time_of_arrival | 
|---|
|  | 148 | self.pdw_format_identifier = pdw_format_identifier | 
|---|
|  | 149 | self.center_frequency = center_frequency | 
|---|
|  | 150 | self.is_valid = is_valid | 
|---|
|  | 151 | self.is_pulse = is_pulse | 
|---|
|  | 152 | self.level_unit = level_unit | 
|---|
|  | 153 | self.signal_start_missing = signal_start_missing | 
|---|
|  | 154 | self.signal_end_missing = signal_end_missing | 
|---|
|  | 155 | self.pulse_width = pulse_width | 
|---|
|  | 156 | self.frequency_shift_or_bandwidth = frequency_shift_or_bandwidth | 
|---|
|  | 157 | self.pulse_level_or_pulse_field_strength = pulse_level_or_pulse_field_strength | 
|---|
|  | 158 | self.region_of_interest = region_of_interest | 
|---|
|  | 159 | self.azimuth_confidence = azimuth_confidence | 
|---|
|  | 160 | self.modulation = modulation | 
|---|
|  | 161 | self.sector = sector | 
|---|
|  | 162 | self.polarity = polarity | 
|---|
|  | 163 | self.df_quality = df_quality | 
|---|
|  | 164 | self.elevation = elevation | 
|---|
|  | 165 | self.azimuth = azimuth | 
|---|
|  | 166 | self.channel = channel | 
|---|
|  | 167 |  | 
|---|
|  | 168 | def __str__(self): | 
|---|
|  | 169 | output = ("Time of arrival: " + str(self.time_of_arrival) + "\n" + | 
|---|
|  | 170 | "PDW Format identifier: " + str(self.pdw_format_identifier) + "\n" + | 
|---|
|  | 171 | "Center frequency: " + str(self.center_frequency) + " KHz\n") | 
|---|
|  | 172 |  | 
|---|
|  | 173 | if self.is_valid: | 
|---|
|  | 174 | output += "Signal: Valid\n" | 
|---|
|  | 175 | else: | 
|---|
|  | 176 | output += "Signal: Invalid\n" | 
|---|
|  | 177 |  | 
|---|
|  | 178 | if self.is_pulse: | 
|---|
|  | 179 | output += "Signal type: Pulse\n" | 
|---|
|  | 180 | else: | 
|---|
|  | 181 | output += "Signal type: Continuous wave\n" | 
|---|
|  | 182 |  | 
|---|
|  | 183 | if self.level_unit == 1: | 
|---|
|  | 184 | output += "Pulse level: " + str(self.pulse_level_or_pulse_field_strength) + " dbµV\n" | 
|---|
|  | 185 | else: | 
|---|
|  | 186 | output += "Pulse field strength: " + str(self.pulse_level_or_pulse_field_strength) + " dbµV/meter\n" | 
|---|
|  | 187 |  | 
|---|
|  | 188 | output += ("Pulse width: " + str(self.pulse_width) + " nanoseconds\n" + | 
|---|
|  | 189 | "Frequency shift or bandwidth: " + str(self.frequency_shift_or_bandwidth) + " KHz\n") | 
|---|
|  | 190 |  | 
|---|
|  | 191 | if self.region_of_interest: | 
|---|
|  | 192 | output += "Region of interest: Yes\n" | 
|---|
|  | 193 | else: | 
|---|
|  | 194 | output += "Region of interest: No\n" | 
|---|
|  | 195 |  | 
|---|
|  | 196 | if self.azimuth_confidence == 6.3: | 
|---|
|  | 197 | output += "Azimuth confidence: Invalid\n" | 
|---|
|  | 198 | else: | 
|---|
|  | 199 | output += "Azimuth confidence: " + str(self.azimuth_confidence) + " degree\n" | 
|---|
|  | 200 |  | 
|---|
|  | 201 | output += "Modulation: " + str(self.modulation) + "\n" | 
|---|
|  | 202 |  | 
|---|
|  | 203 | if self.sector == 0: | 
|---|
|  | 204 | output += "Sector: Unknown\n" | 
|---|
|  | 205 | else: | 
|---|
|  | 206 | output += "Sector:" + str(self.sector) + "\n" | 
|---|
|  | 207 |  | 
|---|
|  | 208 | output += "Polarity: " + str(self.polarity) + "\n" | 
|---|
|  | 209 |  | 
|---|
|  | 210 | output += "DF quality: " + str(self.df_quality) + " %\n" | 
|---|
|  | 211 |  | 
|---|
|  | 212 | if self.elevation == 1024: | 
|---|
|  | 213 | output += "Elevation: Unknown\n" | 
|---|
|  | 214 | else: | 
|---|
|  | 215 | output += "Elevation: " + str(self.elevation) + " degree\n" | 
|---|
|  | 216 |  | 
|---|
|  | 217 | if self.azimuth == 409.5: | 
|---|
|  | 218 | output += "Azimuth: Unknown\n" | 
|---|
|  | 219 | else: | 
|---|
|  | 220 | output += "Azimuth: " + str(self.azimuth) + " degree\n" | 
|---|
|  | 221 |  | 
|---|
|  | 222 | output += "Channel: " + str(self.channel) + "\n" | 
|---|
|  | 223 |  | 
|---|
|  | 224 | return output | 
|---|
|  | 225 |  | 
|---|
|  | 226 |  | 
|---|
|  | 227 | if __name__ == '__main__': | 
|---|
|  | 228 | pass | 
|---|