Changeset a8f6b97 in ammosreader for ammosreader/PDW.py


Ignore:
Timestamp:
09/03/23 17:01:14 (21 months ago)
Author:
Enrico Schwass <ennoausberlin@…>
Branches:
guix
Children:
be36a02
Parents:
2b95c18
Message:

bitstring library added as dependency

lots of properties added, float values removed, scaling and signed integer extension fixed

File:
1 edited

Legend:

Unmodified
Added
Removed
  • ammosreader/PDW.py

    r2b95c18 ra8f6b97  
    44import math
    55import numpy as np
     6from bitstring import BitArray
    67from ammosreader import logger
    78
     
    1314    """
    1415
     16    @classmethod
     17    def polarities(cls):
     18         return {0: 'Horizontal/Unknown', 1: 'Vertical', 2: 'Counter clockwise', 3: 'Clockwise'}
     19
     20    @classmethod
     21    def modulations(cls):
     22        return {0: 'Unknown', 1: 'Unmodulated', 2: 'FM', 3: 'LFM', 4: 'PSK-2', 5: 'PSK-3', 6: 'PSK-4',
     23                7: 'PSK-m', 8: 'NLFM', 9: 'SFM', 10: 'TFM', 11: 'Pulse too short'}
     24   
    1525    @classmethod
    1626    def from_bytes(cls, byte_string):
     
    3646        if nanoseconds >= unix_time:
    3747            raise OverflowError("Timestamp invalid")
    38         time_of_arrival = np.datetime64(nanoseconds, 'ns')
    39 
    40         third_entry = bin(int.from_bytes(parts[1], byteorder='little'))
    41         padding = 32-len(str(third_entry)[2:])
    42         third_entry_bit_string = "0" * padding + str(third_entry)[2:]
    43         pdw_format_identifier = int(third_entry_bit_string[0:6], 2)
    44         center_frequency = int(third_entry_bit_string[5:32], 2)
    45 
    46         fourth_entry = bin(int.from_bytes(parts[2], byteorder='little'))
    47         padding = 32-len(str(fourth_entry)[2:])
    48         fourth_entry_bit_string = "0" * padding + str(fourth_entry)[2:]
    49         is_valid = bool(int(fourth_entry_bit_string[0]))
    50         is_pulse = bool(int(fourth_entry_bit_string[1]))
    51         level_unit = bool(int(fourth_entry_bit_string[2]))
    52         signal_start_missing = bool(int(fourth_entry_bit_string[3]))
    53         signal_end_missing = bool(int(fourth_entry_bit_string[4]))
    54         pulse_width = int(fourth_entry_bit_string[7:33], 2)
    55 
    56         fifth_entry = bin(int.from_bytes(parts[3], byteorder='little'))
    57         padding = 32-len(str(fifth_entry)[2:])
    58         fifth_entry_bit_string = "0" * padding + str(fifth_entry)[2:]
    59         frequency_shift_or_bandwidth = int(fifth_entry_bit_string[0:20], 2)
    60         # FIXME: You have to scale me to the range from -200.0 to 200.0 in 0.1 steps
    61         pulse_level_or_pulse_field_strength = math.ceil(int(fifth_entry_bit_string[20:32], 2)) / 10
    62 
    63         sixth_entry = bin(int.from_bytes(parts[4], byteorder='little'))
    64         padding = 32-len(str(sixth_entry)[2:])
    65         sixth_entry_bit_string = "0" * padding + str(sixth_entry)[2:]
    66         region_of_interest = bool(int(sixth_entry_bit_string[0]))
    67         # FIXME: You have to scale me to a range from 0.0 to 6.2 in steps of 0.1 - 6.3 means unknown
    68         azimuth_confidence = math.ceil(int(sixth_entry_bit_string[1:7], 2)) / 10
    69         modulations = {0: 'Unknown', 1: 'Unmodulated', 2: 'FM', 3: 'LFM', 4: 'PSK-2', 5: 'PSK-3', 6: 'PSK-4',
    70                        7: 'PSK-m', 8: 'NLFM', 9: 'SFM', 10: 'TFM', 11: 'Pulse too short'}
    71         modulation = modulations.get(int(sixth_entry_bit_string[7:12], 2), 0)
    72         sector = int(sixth_entry_bit_string[28:32], 2)
    73 
    74         seventh_entry = bin(int.from_bytes(parts[5], byteorder='little'))
    75         padding = 32-len(str(seventh_entry)[2:])
    76         seventh_entry_bit_string = "0" * padding + str(seventh_entry)[2:]
    77         polarities = {0: 'Horizontal/Unknown', 1: 'Vertical', 2: 'Counter clockwise', 3: 'Clockwise'}
    78         polarity = polarities[int(seventh_entry_bit_string[0:2], 2)]
    79         df_quality = int(seventh_entry_bit_string[2:9], 2)
    80         # FIXME: You have to scale me from -90 to 90 in 0.1 degree steps
    81         elevation = int(seventh_entry_bit_string[9:20], 2) / 10
    82         # FIXME: You have to check me for a range from 0.0 to 359.9 in steps of 0.1
    83         azimuth = 0.1 * (int(seventh_entry_bit_string[20:32], 2))
    84 
    85         eighth_entry = bin(int.from_bytes(parts[5], byteorder='little'))
    86         padding = 32-len(str(eighth_entry)[2:])
    87         eighth_entry_bit_string = "0" * padding + str(eighth_entry)[2:]
    88         channel = int(eighth_entry_bit_string[0:4], 2)
     48        time_of_arrival = nanoseconds
     49
     50        third_word = bin(int.from_bytes(parts[1], byteorder='little'))
     51        padding = 32-len(str(third_word)[2:])
     52        third_word_bit_string = "0" * padding + str(third_word)[2:]
     53        pdw_format_identifier = int(third_word_bit_string[0:6], 2)
     54        center_frequency = int(third_word_bit_string[5:32], 2) # Value of zero means unknown
     55       
     56        fourth_word = bin(int.from_bytes(parts[2], byteorder='little'))
     57        padding = 32-len(str(fourth_word)[2:])
     58        fourth_word_bit_string = "0" * padding + str(fourth_word)[2:]
     59        is_valid = bool(int(fourth_word_bit_string[0]))
     60        is_pulse = bool(int(fourth_word_bit_string[1]))
     61        level_unit = int(fourth_word_bit_string[2])
     62        signal_start_missing = bool(int(fourth_word_bit_string[3]))
     63        signal_end_missing = bool(int(fourth_word_bit_string[4]))
     64        pulse_width = int(fourth_word_bit_string[7:33], 2)
     65
     66        fifth_word = bin(int.from_bytes(parts[3], byteorder='little'))
     67        padding = 32-len(str(fifth_word)[2:])
     68        fifth_word_bit_string = "0" * padding + str(fifth_word)[2:]
     69        frequency_shift_or_bandwidth = int(fifth_word_bit_string[0:20], 2)
     70        pulse_level_or_pulse_field_strength = BitArray(fifth_word_bit_string[20:32]).int
     71
     72        sixth_word = bin(int.from_bytes(parts[4], byteorder='little'))
     73        padding = 32-len(str(sixth_word)[2:])
     74        sixth_word_bit_string = "0" * padding + str(sixth_word)[2:]
     75        region_of_interest = bool(int(sixth_word_bit_string[0]))
     76        azimuth_confidence = int(sixth_word_bit_string[1:7], 2)
     77        modulation = int(sixth_word_bit_string[7:12], 2)
     78        sector = int(sixth_word_bit_string[28:32], 2)
     79
     80        seventh_word = bin(int.from_bytes(parts[5], byteorder='little'))
     81        padding = 32-len(str(seventh_word)[2:])
     82        seventh_word_bit_string = "0" * padding + str(seventh_word)[2:]
     83        polarity = int(seventh_word_bit_string[0:2], 2)
     84        df_quality = int(seventh_word_bit_string[2:9], 2)
     85        elevation = BitArray(bin=seventh_word_bit_string[9:20]).int
     86        azimuth = int(seventh_word_bit_string[20:32], 2)
     87
     88        eighth_word = bin(int.from_bytes(parts[5], byteorder='little'))
     89        padding = 32-len(str(eighth_word)[2:])
     90        eighth_word_bit_string = "0" * padding + str(eighth_word)[2:]
     91        channel = int(eighth_word_bit_string[0:4], 2)
    8992
    9093        return PDW(time_of_arrival, pdw_format_identifier, center_frequency, is_valid, is_pulse, level_unit,
     
    110113        :param is_pulse: flag to mark if pdw data body contains a pulse or a continuous wave signal
    111114        :type is_pulse: Boolean
    112         :param level_unit: 0 means dBµV - 1 means dBµV/m
     115        :param level_unit: 1 means dBµV - 0 means dBµV/m
    113116        :type level_unit: Integer
    114117        :param signal_start_missing: signal started before time of arrival
     
    121124        :type frequency_shift_or_bandwidth: Integer
    122125        :param pulse_level_or_pulse_field_strength: Pulse level or Pulse Field Strength depending on level_unit \
    123          (-200.0...200.0) in 0.1 steps / minus 204.8 means no valid level detected
    124         :type pulse_level_or_pulse_field_strength: Float
     126         (-200.0...200.0) in tenth degrees / minus 2048 means no valid level detected
     127        :type pulse_level_or_pulse_field_strength: Integer
    125128        :param region_of_interest: Marks if signal is from region of interest
    126129        :type region_of_interest: Boolean
    127         :param azimuth_confidence: degree in steps of 0.1 (0.0-6.2) / 6.3 means confidence unknown
    128         :type azimuth_confidence: Float
     130        :param azimuth_confidence: degree in tenth steps of (0-62) / 63 means confidence unknown
     131        :type azimuth_confidence: Integer
    129132        :param modulation: type of modulation (e.g. PSK-2, PSK-4, FM etc.)
    130133        :type modulation: String
     
    135138        :param df_quality: Direction finding quality in percent (0-100) - Zero means unknown
    136139        :type df_quality: Integer
    137         :param elevation: elevation of incoming signal (from -90 to 90 degree) in steps of 0.1 degree \
    138         minus 102.4 means unknown
    139         :type elevation: Float
    140         :param azimuth: azimuth of incoming signal (from 0 to 359.9 degree) in steps of 0.1 degree \
    141         plus 409.5 means unknown
    142         :type azimuth: Float
     140        :param elevation: elevation of incoming signal (from -90 to 90 degree) in steps tenths degree \
     141        minus 1024 means unknown
     142        :type elevation: Integer
     143        :param azimuth: azimuth of incoming signal (from 0 to 3599 tenth degree) plus 4095 means unknown
     144        :type azimuth: Integer
    143145        :param channel: detecting channel (0-16) - Zero means unknown
    144146        :type channel: Integer
     
    146148        :rtype: PDW
    147149        """
    148         self.time_of_arrival = time_of_arrival #
    149         self.pdw_format_identifier = pdw_format_identifier
    150         self.center_frequency = center_frequency #
     150        self.__time_of_arrival = time_of_arrival #
     151        self.__pdw_format_identifier = pdw_format_identifier
     152        self.__center_frequency = center_frequency #
    151153        self.__is_valid = is_valid #
    152         self.is_pulse = is_pulse #
    153         self.level_unit = level_unit #
    154         self.signal_start_missing = signal_start_missing
    155         self.signal_end_missing = signal_end_missing
    156         self.pulse_width = pulse_width #
    157         self.frequency_shift_or_bandwidth = frequency_shift_or_bandwidth #
    158         self.pulse_level_or_pulse_field_strength = pulse_level_or_pulse_field_strength #
    159         self.region_of_interest = region_of_interest
    160         self.azimuth_confidence = azimuth_confidence
    161         self.modulation = modulation #
    162         self.sector = sector
    163         self.polarity = polarity
    164         self.df_quality = df_quality #
    165         self.elevation = elevation #
    166         self.azimuth = azimuth
    167         self.channel = channel #
     154        self.__is_pulse = is_pulse #
     155        self.__level_unit = level_unit #
     156        self.__signal_start_missing = signal_start_missing
     157        self.__signal_end_missing = signal_end_missing
     158        self.__pulse_width = pulse_width #
     159        self.__frequency_shift_or_bandwidth = frequency_shift_or_bandwidth #
     160        self.__pulse_level_or_pulse_field_strength = pulse_level_or_pulse_field_strength #
     161        self.__region_of_interest = region_of_interest
     162        self.__azimuth_confidence = azimuth_confidence
     163        self.__modulation = modulation #
     164        self.__sector = sector
     165        self.__polarity = polarity
     166        self.__df_quality = df_quality #
     167        self.__elevation = elevation #
     168        self.__azimuth = azimuth
     169        self.__channel = channel #
    168170
    169171    def __str__(self):
     
    177179                  "Center frequency: " + str(self.center_frequency) + " KHz\n")
    178180
    179         if self.__is_valid:
     181        if self.is_valid:
    180182            output += "Signal: Valid\n"
    181183        else:
     
    187189            output += "Signal type: Continuous wave\n"
    188190
    189         if self.level_unit == 1:
     191        # FIXME: use fstrings or another better performance string concat
     192        if self.level_unit == "dbµV":
    190193            output += "Pulse level: " + str(self.pulse_level_or_pulse_field_strength) + " dbµV\n"
    191194        else:
    192             output += "Pulse field strength: " + str(self.pulse_level_or_pulse_field_strength) + " dbµV/meter\n"
     195            output += "Pulse field strength: " + str(self.pulse_level_or_pulse_field_strength) + " dbµV/m\n"
    193196
    194197        output += ("Pulse width: " + str(self.pulse_width) + " nanoseconds\n" +
     
    200203            output += "Region of interest: No\n"
    201204
    202         if self.azimuth_confidence == 6.3:
     205        if self.azimuth_confidence == None:
    203206            output += "Azimuth confidence: Invalid\n"
    204207        else:
     
    207210        output += "Modulation: " + str(self.modulation) + "\n"
    208211
    209         if self.sector == 0:
     212        if self.sector == None:
    210213            output += "Sector: Unknown\n"
    211214        else:
     
    216219        output += "DF quality: " + str(self.df_quality) + " %\n"
    217220
    218         if self.elevation == 102.4:
     221        if self.elevation == None:
    219222            output += "Elevation: Unknown\n"
    220223        else:
    221224            output += "Elevation: " + str(self.elevation) + " degree\n"
    222225
    223         if self.azimuth == 409.5:
     226        if self.azimuth == None:
    224227            output += "Azimuth: Unknown\n"
    225228        else:
     
    230233        return output
    231234
     235    @property
     236    def time_of_arrival(self):
     237        return np.datetime64(self.__time_of_arrival, 'ns')
     238   
     239    @property
     240    def center_frequency(self):
     241        if self.__center_frequency == 0:
     242            return None
     243        return self.__center_frequency
     244
     245    @property
    232246    def is_valid(self):
    233247        return self.__is_valid
    234248
     249    @property
     250    def is_invalid(self):
     251        return not self.__is_valid
     252
     253    @property
     254    def is_pulse(self):
     255        return self.__is_pulse
     256
     257    @property
     258    def is_cw(self):
     259        return not self.__is_pulse
     260
     261    @property
     262    def level_unit(self):
     263        if self.__level_unit == 1:
     264            return "dbµV"
     265        else:
     266            return "dbµV/m"
     267
     268    @property
     269    def signal_start_missing(self):
     270        return self.__signal_start_missing
     271
     272    @property
     273    def signal_end_missing(self):
     274        return self.__signal_end_missing
     275
     276    @property
     277    def pulse_width(self):
     278        if self.__pulse_width == 0:
     279            return None
     280        return self.__pulse_width
     281
     282    @property
     283    def frequency_shift_or_bandwidth(self):
     284        if self.__frequency_shift_or_bandwidth == 1048575:
     285            return None
     286        return self.__frequency_shift_or_bandwidth
     287
     288    @property
     289    def pulse_level_or_pulse_field_strength(self):
     290        if self.__pulse_level_or_pulse_field_strength == -2048:
     291            return None
     292        return (self.__pulse_level_or_field_strength / 10)
     293
     294    @property
     295    def azimuth_confidence(self):
     296        if azimuth_confidence == 63:
     297            return None
     298        return (azimuth_confidence / 10)
     299
     300    @property
     301    def modulation(self):
     302        return PDW.modulations().get(self.__modulation, PDW.modulations()[0])
     303   
     304    @property
     305    def region_of_interest(self):
     306        return self.__region_of_interest
     307
     308    @property
     309    def sector(self):
     310        if self.__sector == 0:
     311            return None
     312        return self.__sector
     313
     314    @property
     315    def polarity(self):
     316        return PDW.polarities().get(self.__polarity, PDW.polarities()[0])
     317
     318    @property
     319    def elevation(self):
     320        if self.__elevation == -1024:
     321            return None
     322        return self.__elevation / 10
     323
     324    @property
     325    def azimuth(self):
     326        if self.__azimuth == 4095:
     327            return None
     328        return self.__azimuth / 10
     329
     330    @property
     331    def channel(self):
     332        return self.__channel
     333   
    235334    def to_datacrunch_json(self):
    236335        import uuid
     
    238337                'FORMAT': self.pdw_format_identifier,
    239338                'CENTER_FREQUENCY': self.center_frequency,
    240                 'VALID_FLAG': self.__is_valid,
     339                'VALID_FLAG': self.is_valid,
    241340                'PULSE_FLAG': self.is_pulse,
    242341                'LU_FLAG': self.level_unit,
     
    259358
    260359    def to_json(self):
    261         return {'time of arrival': self.time_of_arrival,
     360        return {'TIME OF ARRIVAL': self.time_of_arrival,
    262361                'FORMATIDENTIFIER': self.pdw_format_identifier,
    263                 'center frequency': self.center_frequency,
    264                 'VALID': self.__is_valid,
     362                'CENTER FREQUENCY': self.center_frequency,
     363                'VALID': self.is_valid,
    265364                'PULSE': self.is_pulse,
    266365                'PULSELEVEL': self.pulse_level_or_pulse_field_strength,
Note: See TracChangeset for help on using the changeset viewer.