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')
|
---|
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 | r"""
|
---|
97 | I return an instance of an Pulse Data word.
|
---|
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 | self.time_of_arrival = time_of_arrival
|
---|
146 | self.pdw_format_identifier = pdw_format_identifier
|
---|
147 | self.center_frequency = center_frequency
|
---|
148 | self.is_valid = is_valid
|
---|
149 | self.is_pulse = is_pulse
|
---|
150 | self.level_unit = level_unit
|
---|
151 | self.signal_start_missing = signal_start_missing
|
---|
152 | self.signal_end_missing = signal_end_missing
|
---|
153 | self.pulse_width = pulse_width
|
---|
154 | self.frequency_shift_or_bandwidth = frequency_shift_or_bandwidth
|
---|
155 | self.pulse_level_or_pulse_field_strength = pulse_level_or_pulse_field_strength
|
---|
156 | self.region_of_interest = region_of_interest
|
---|
157 | self.azimuth_confidence = azimuth_confidence
|
---|
158 | self.modulation = modulation
|
---|
159 | self.sector = sector
|
---|
160 | self.polarity = polarity
|
---|
161 | self.df_quality = df_quality
|
---|
162 | self.elevation = elevation
|
---|
163 | self.azimuth = azimuth
|
---|
164 | self.channel = channel
|
---|
165 |
|
---|
166 | def __str__(self):
|
---|
167 | output = ("Time of arrival: " + str(self.time_of_arrival) + "\n" +
|
---|
168 | "PDW Format identifier: " + str(self.pdw_format_identifier) + "\n" +
|
---|
169 | "Center frequency: " + str(self.center_frequency) + " KHz\n")
|
---|
170 |
|
---|
171 | if self.is_valid:
|
---|
172 | output += "Signal: Valid\n"
|
---|
173 | else:
|
---|
174 | output += "Signal: Invalid\n"
|
---|
175 |
|
---|
176 | if self.is_pulse:
|
---|
177 | output += "Signal type: Pulse\n"
|
---|
178 | else:
|
---|
179 | output += "Signal type: Continuous wave\n"
|
---|
180 |
|
---|
181 | if self.level_unit == 1:
|
---|
182 | output += "Pulse level: " + str(self.pulse_level_or_pulse_field_strength) + " dbµV\n"
|
---|
183 | else:
|
---|
184 | output += "Pulse field strength: " + str(self.pulse_level_or_pulse_field_strength) + " dbµV/meter\n"
|
---|
185 |
|
---|
186 | output += ("Pulse width: " + str(self.pulse_width) + " nanoseconds\n" +
|
---|
187 | "Frequency shift or bandwidth: " + str(self.frequency_shift_or_bandwidth) + " KHz\n")
|
---|
188 |
|
---|
189 | if self.region_of_interest:
|
---|
190 | output += "Region of interest: Yes\n"
|
---|
191 | else:
|
---|
192 | output += "Region of interest: No\n"
|
---|
193 |
|
---|
194 | if self.azimuth_confidence == 6.3:
|
---|
195 | output += "Azimuth confidence: Invalid\n"
|
---|
196 | else:
|
---|
197 | output += "Azimuth confidence: " + str(self.azimuth_confidence) + " degree\n"
|
---|
198 |
|
---|
199 | output += "Modulation: " + str(self.modulation) + "\n"
|
---|
200 |
|
---|
201 | if self.sector == 0:
|
---|
202 | output += "Sector: Unknown\n"
|
---|
203 | else:
|
---|
204 | output += "Sector:" + str(self.sector) + "\n"
|
---|
205 |
|
---|
206 | output += "Polarity: " + str(self.polarity) + "\n"
|
---|
207 |
|
---|
208 | output += "DF quality: " + str(self.df_quality) + " %\n"
|
---|
209 |
|
---|
210 | if self.elevation == 1024:
|
---|
211 | output += "Elevation: Unknown\n"
|
---|
212 | else:
|
---|
213 | output += "Elevation: " + str(self.elevation) + " degree\n"
|
---|
214 |
|
---|
215 | if self.azimuth == 409.5:
|
---|
216 | output += "Azimuth: Unknown\n"
|
---|
217 | else:
|
---|
218 | output += "Azimuth: " + str(self.azimuth) + " degree\n"
|
---|
219 |
|
---|
220 | output += "Channel: " + str(self.channel) + "\n"
|
---|
221 |
|
---|
222 | return output
|
---|
223 |
|
---|
224 |
|
---|
225 | if __name__ == '__main__':
|
---|
226 | pass
|
---|