├── mpegtsdata ├── __init__.py ├── streams.py ├── utils.py ├── pes.py ├── packet.py └── adaptaionfield.py ├── klvdata ├── __init__.py ├── streamparser.py ├── klvparser.py ├── element.py ├── misb0102.py ├── elementparser.py ├── common.py ├── setparser.py └── misb0601.py ├── klvreconstructor.py ├── run.py ├── README.md └── .gitignore /mpegtsdata/__init__.py: -------------------------------------------------------------------------------- 1 | from .streams import extract_streams 2 | -------------------------------------------------------------------------------- /klvdata/__init__.py: -------------------------------------------------------------------------------- 1 | from .streamparser import StreamParser 2 | from . import misb0601 3 | from . import misb0102 -------------------------------------------------------------------------------- /mpegtsdata/streams.py: -------------------------------------------------------------------------------- 1 | from .packet import extract_packet 2 | from .utils import get_or_create_key 3 | 4 | packet_size = 188 5 | 6 | 7 | def extract_streams(stream): 8 | """ 9 | Extract MPEG-TS packets per stream 10 | :param stream: MPEG-TS data stream, usually from a file 11 | :return: Dictionary of stream id (key) and list of packets (value) 12 | """ 13 | streams_packets = {} 14 | packet_data = stream.read(packet_size) 15 | while packet_data: 16 | packet = extract_packet(packet_data) 17 | stream_packets = get_or_create_key(streams_packets, packet['packet_id'], []) 18 | stream_packets.append(packet) 19 | packet_data = stream.read(packet_size) 20 | return streams_packets 21 | -------------------------------------------------------------------------------- /klvreconstructor.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | 3 | # universal_key = binascii.unhexlify('060e2b34020b0101') 4 | universal_key = binascii.unhexlify('060e2b34020b0101') 5 | 6 | def reconstruct_klv_packets(packets): 7 | """ 8 | Reconstruct KLV packets from TS packets 9 | :param packets: MPEG-TS packets 10 | :return: Buffer containing the KLV payload, list of pts per packet 11 | """ 12 | is_found = False 13 | buffer = b'' 14 | pts_per_packet = [] 15 | 16 | for packet in packets: 17 | if packet["payload_unit_start_indicator"]: 18 | is_found = universal_key in packet['payload'] 19 | if is_found: 20 | pts_per_packet.append(packet['pes']['pts'] if ('pes' in packet and 'pts' in packet['pes']) else None) 21 | key_index = packet['payload'].index(universal_key) 22 | buffer += packet['payload'][key_index:] 23 | elif is_found: 24 | buffer += packet['payload'] 25 | return buffer, pts_per_packet 26 | -------------------------------------------------------------------------------- /mpegtsdata/utils.py: -------------------------------------------------------------------------------- 1 | def zero_fill_right_shift(val, n): 2 | return (val >> n) if val >= 0 else ((val + 0x100000000) >> n) 3 | 4 | 5 | def read_unsigned_int32_be(data, index): 6 | return int.from_bytes(data[index:index + 4], byteorder='big', signed=False) 7 | 8 | 9 | def read_unsigned_int_be(data, index, length): 10 | return int.from_bytes(data[index:index + length], byteorder='big', signed=False) 11 | 12 | 13 | def is_packet_start(data): 14 | return data[0] == 0x47 15 | 16 | 17 | def get_timestamp(data, index): 18 | timestamp = (read_unsigned_int_be(data, index, 1) & 0x0E) * 536870912 19 | timestamp += (read_unsigned_int_be(data, index + 1, 2) & 0xFFFE) * 16384 20 | timestamp += int(read_unsigned_int_be(data, index + 3, 2) / 2) 21 | return timestamp 22 | 23 | 24 | def get_or_create_key(dict, key, default_value): 25 | if key in dict: 26 | value = dict[key] 27 | else: 28 | value = default_value 29 | dict[key] = value 30 | return value 31 | -------------------------------------------------------------------------------- /mpegtsdata/pes.py: -------------------------------------------------------------------------------- 1 | from .utils import * 2 | 3 | 4 | def extract_pes(data): 5 | if len(data) < 3: 6 | return None 7 | if read_unsigned_int_be(data, 0, 3) != 1: 8 | return None 9 | 10 | optional_header = read_unsigned_int_be(data, 6, 2) 11 | pes = { 12 | 'packet_start_code_prefix': read_unsigned_int_be(data, 0, 3), 13 | 'stream_id': read_unsigned_int_be(data, 3, 1), 14 | 'packet_length': read_unsigned_int_be(data, 4, 2), 15 | 'scrambling_control': zero_fill_right_shift(optional_header & 0x3000, 12), 16 | 'priority': (optional_header & 0x800) != 0, 17 | 'data_alignment_indicator': (optional_header & 0x400) != 0, 18 | 'copyright': (optional_header & 0x200) != 0, 19 | 'original_or_copy': (optional_header & 0x100) != 0, 20 | 'pts_dts_flags': (optional_header & 0xc0) >> 6, 21 | 'escr_flag': (optional_header & 0x20) != 0, 22 | 'sr_rate_flag': (optional_header & 0x10) != 0, 23 | 'dsm_trick_mode_flag': (optional_header & 0x08) != 0, 24 | 'additional_copy_info_flag': (optional_header & 0x04) != 0, 25 | 'crc_flag': (optional_header & 0x02) != 0, 26 | 'extension_flag': (optional_header & 0x01) != 0, 27 | 'header_data_length': read_unsigned_int_be(data, 8, 1) 28 | } 29 | 30 | if pes['pts_dts_flags'] & 0x2: 31 | pes['pts'] = get_timestamp(data, 9) 32 | 33 | if pes['pts_dts_flags'] & 0x1: 34 | pes['dts'] = get_timestamp(data, 14) 35 | 36 | return pes 37 | -------------------------------------------------------------------------------- /mpegtsdata/packet.py: -------------------------------------------------------------------------------- 1 | from .adaptaionfield import * 2 | from .pes import * 3 | from .utils import * 4 | 5 | 6 | def extract_packet(data): 7 | if not is_packet_start(data): 8 | return None 9 | packet = { 10 | 'sync_byte': data[0], 11 | 'transport_error_indicator': zero_fill_right_shift(data[1] & 0x80, 7), 12 | 'payload_unit_start_indicator': zero_fill_right_shift(data[1] & 0x40, 6), 13 | 'transport_priority': zero_fill_right_shift(data[1] & 0x20, 5), 14 | 'packet_id': ((data[1] & 0x1F) << 8) | data[2], 15 | 'transport_scrambling_control': zero_fill_right_shift(data[3] & 0xC0, 6), 16 | 'adaptation_field_control': zero_fill_right_shift(data[3] & 0x30, 4), 17 | 'continuity_counter': data[3] & 0x0F, 18 | 'payload_index': len(data) 19 | } 20 | 21 | field_control = packet['adaptation_field_control'] 22 | if (field_control == 0x02) or (field_control == 0x03): # Adaptation field 23 | packet['adaptation_field'] = extract_adaptation_field(data) 24 | 25 | if field_control == 0x01: 26 | packet['payload_index'] = 4 27 | elif field_control == 0x03: 28 | packet['payload_index'] = 5 + packet['adaptation_field']['field_length'] 29 | 30 | if packet['payload_index'] > len(data): 31 | print(f'Packet has payload start bigger then length: {packet["payload_index"]} >= {len(data)}') 32 | 33 | packet['payload'] = data[packet['payload_index']:] 34 | packet['pes'] = extract_pes(packet['payload']) 35 | return packet 36 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import klvdata 3 | import mpegtsdata 4 | from klvreconstructor import reconstruct_klv_packets 5 | 6 | 7 | def extract_klv(path): 8 | with open(path, 'rb') as stream: 9 | for packet in klvdata.StreamParser(stream): 10 | packet.structure() 11 | packet.validate() 12 | 13 | 14 | def extract_mpegts(path): 15 | with open(path, 'rb') as stream: 16 | streams_packets = mpegtsdata.extract_streams(stream) 17 | for key in streams_packets: 18 | packets = streams_packets[key] 19 | print(f'key=0x{key:X}, packets: {len(packets)}') 20 | klv_data, pts_per_packet = reconstruct_klv_packets(packets) 21 | total_klv_packets = len(pts_per_packet) 22 | if total_klv_packets: 23 | print(f'Reconstructed packets: {total_klv_packets}') 24 | index = 0 25 | for packet in klvdata.StreamParser(klv_data): 26 | print(f'Packet pts: {pts_per_packet[index]}') 27 | index += 1 28 | packet.structure() 29 | packet.validate() 30 | 31 | 32 | if __name__ == '__main__': 33 | parser = argparse.ArgumentParser() 34 | parser.add_argument('-f', '--file', help='File stream to extract', required=True) 35 | parser.add_argument('-k', '--klv', help='Is file KLV (true) or MPEG-TS (false, default)', 36 | default=False, action='store_true') 37 | args = parser.parse_args() 38 | 39 | if args.klv: 40 | extract_klv(args.file) 41 | else: 42 | extract_mpegts(args.file) 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KLV Over MPEG-TS Extractor 2 | Extract MISB 601 KLV data from MPEG-TS stream (STANAG 4609). 3 | 4 | ## Credit 5 | KLV Data is adapted from https://github.com/paretech/klvdata. 6 | 7 | The code changes include the following: 8 | * Ignore errors (write -1000 or -2000 as value). 9 | * Output readable value when printing structure. 10 | * Adding CRC validation to packet. 11 | 12 | ## Implementation 13 | MPEG-TS extraction is partly implemented, many unneeded fields are not extracted. 14 | Packet ID extraction doesn't seem to be correct when comparing to FFMPEG. 15 | 16 | The code is exposed a CLI but can be used as a package to extract KLV data. 17 | 18 | ## Getting Started 19 | Clone the repository. 20 | Use an IDE such as PyCharm. 21 | Create a virtual environment fro the project using python 3.6+. 22 | No additional dependencies are required. 23 | 24 | ## Extracting KLV 25 | The code can run on entire MPEG-TS file or extracted KLV binary file. 26 | The extract KLV binary use the following: 27 | * Using tool such as ffprobe get the mapping of KLV stream, e.g. 0:3 28 | * Using tool such as ffmpeg copy the KLV stream to file, e.g.: 29 | ```bash 30 | ffmpeg -i [video-name].ts -map 0:3 -c copy -f data [video-name].klv 31 | ``` 32 | 33 | ## Usage 34 | run.py is a CLI for parsing klv/mpeg-ts files. 35 | As output can be large, depending on input file, it is advised to pipe to log file. 36 | ### KLV binary file 37 | ```bash 38 | python run.py -f [file.klv] -k 39 | ``` 40 | 41 | ### MPEG-TS video file 42 | ```bash 43 | python run.py -f [file.ts] 44 | ``` 45 | -------------------------------------------------------------------------------- /mpegtsdata/adaptaionfield.py: -------------------------------------------------------------------------------- 1 | from .utils import * 2 | 3 | 4 | def extract_adaptation_optional_fields(data, field): 5 | index = 6 6 | optional_fields = {} 7 | 8 | if field['pcr_flag']: 9 | optional_fields['pcr_base'] = read_unsigned_int32_be(data, index) * 2 + \ 10 | zero_fill_right_shift(data[index + 4] & 0x80, 7) 11 | optional_fields['pcr_extension'] = (data[index + 4] & 0x1 << 8) + data[index + 5] 12 | index += 6 13 | 14 | if field['opcr_flag']: 15 | optional_fields['opcr_base'] = read_unsigned_int32_be(data, index) * 2 + \ 16 | zero_fill_right_shift(data[index + 4] & 0x80, 7) 17 | optional_fields['opcr_extension'] = (data[index + 4] & 0x1 << 8) + data[index + 5] 18 | index += 6 19 | 20 | if field['splicing_point_flag']: 21 | optional_fields['splice_countdown'] = data[index] 22 | index += 1 23 | 24 | if field['transport_private_data_flag']: 25 | optional_fields['transport_private_data_length'] = data[index] 26 | optional_fields['private_data'] = None # TODO 27 | index += 1 + field['transport_private_data_flag'] 28 | 29 | # TODO extract other fields 30 | 31 | return optional_fields 32 | 33 | 34 | def extract_adaptation_field(data): 35 | length = data[4] 36 | 37 | if length <= 0: 38 | return {'field_length': length} 39 | 40 | field = { 41 | 'field_length': length, 42 | 'discontinuity_indicator': zero_fill_right_shift(data[5] & 0x80, 7), 43 | 'random_access_indicator': zero_fill_right_shift(data[5] & 0x40, 6), 44 | 'elementary_stream_priority_indicator': zero_fill_right_shift(data[5] & 0x20, 5), 45 | 'pcr_flag': zero_fill_right_shift(data[5] & 0x10, 4), 46 | 'opcr_flag': zero_fill_right_shift(data[5] & 0x08, 3), 47 | 'splicing_point_flag': zero_fill_right_shift(data[5] & 0x04, 2), 48 | 'transport_private_data_flag': zero_fill_right_shift(data[5] & 0x02, 1), 49 | 'adaptation_field_extension_flag': data[5] & 0x01 50 | } 51 | field['optional_fields'] = extract_adaptation_optional_fields(data, field) 52 | return field 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /klvdata/streamparser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2017 Matthew Pare (paretech@gmail.com) 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | from klvdata.element import UnknownElement 27 | 28 | from klvdata.klvparser import KLVParser 29 | 30 | 31 | class StreamParser: 32 | parsers = {} 33 | 34 | def __init__(self, source): 35 | self.source = source 36 | 37 | # All keys in parser are expected to be 16 bytes long. 38 | self.iter_stream = KLVParser(self.source, key_length=11) 39 | 40 | def __iter__(self): 41 | return self 42 | 43 | def __next__(self): 44 | key, value = next(self.iter_stream) 45 | 46 | if key in self.parsers: 47 | return self.parsers[key](value) 48 | else: 49 | # Even if KLV is not known, make best effort to parse and preserve. 50 | # Element is an abstract super class, do not create instances on Element. 51 | return UnknownElement(key, value) 52 | 53 | @classmethod 54 | def add_parser(cls, obj): 55 | """Decorator method used to register a parser to the class parsing repertoire. 56 | 57 | obj is required to implement key attribute supporting bytes as returned by KLVParser key. 58 | """ 59 | 60 | cls.parsers[bytes(obj.key)] = obj 61 | 62 | return obj 63 | -------------------------------------------------------------------------------- /klvdata/klvparser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2016 Matthew Pare (paretech@gmail.com) 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | from io import BytesIO 27 | from io import IOBase 28 | from klvdata.common import bytes_to_int 29 | 30 | 31 | class KLVParser(object): 32 | """Return key, value pairs parsed from an SMPTE ST 336 source.""" 33 | def __init__(self, source, key_length): 34 | if isinstance(source, IOBase): 35 | self.source = source 36 | else: 37 | self.source = BytesIO(source) 38 | 39 | self.key_length = key_length 40 | 41 | def __iter__(self): 42 | return self 43 | 44 | def __next__(self): 45 | key = self.__read(self.key_length) 46 | 47 | # if (key.find(b'\x00\x00\x06\x0e+4\x02\x0b\x01\x01\x0e\x01\x03\x01\x01') > 0): 48 | # key = b'\x06\x0e+4\x02\x0b\x01\x01\x0e\x01\x03\x01\x01\x00\x00\x00' 49 | if (key.find(b'\x0b\x01\x01\x0e\x01\x03\x01\x01') > 0): 50 | key = b'\x0b\x01\x01\x0e\x01\x03\x01\x01\x00\x00\x00' 51 | byte_length = bytes_to_int(self.__read(4)) 52 | else: 53 | byte_length = bytes_to_int(self.__read(1)) 54 | 55 | if byte_length < 128: 56 | # BER Short Form 57 | length = byte_length 58 | else: 59 | # BER Long Form 60 | length = bytes_to_int(self.__read(byte_length - 128)) 61 | 62 | value = self.__read(length) 63 | 64 | return key, value 65 | 66 | def __read(self, size): 67 | if size == 0: 68 | return b'' 69 | 70 | assert size > 0 71 | 72 | data = self.source.read(size) 73 | 74 | if data: 75 | return data 76 | else: 77 | raise StopIteration 78 | 79 | -------------------------------------------------------------------------------- /klvdata/element.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2017 Matthew Pare (paretech@gmail.com) 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | from abc import ABCMeta 27 | from abc import abstractmethod 28 | from klvdata.common import ber_encode 29 | 30 | 31 | # Proposed alternate names, "BaseElement" of modules "bases". 32 | class Element(metaclass=ABCMeta): 33 | """Construct a key, length, value tuplet. 34 | 35 | Elements provide the basic mechanisms to constitute the basic encoding 36 | requirements of key, length, value tuplet as specified by STMPE 336. 37 | 38 | The length is dynamically calculated based off the value. 39 | 40 | Attributes: 41 | key 42 | value 43 | 44 | Properties: 45 | name: If name is set return name, else return class name. 46 | length: Length is calculated based off value. 47 | """ 48 | 49 | def __init__(self, key, value): 50 | self.key = key 51 | self.value = value 52 | 53 | @property 54 | def name(self): 55 | return self.__class__.__name__ 56 | 57 | @property 58 | def length(self): 59 | """bytes: Return the BER encoded byte length of self.value.""" 60 | return ber_encode(len(self)) 61 | 62 | def __bytes__(self): 63 | """Return the MISB encoded representation of a Key, Length, Value element.""" 64 | return bytes(self.key) + bytes(self.length) + bytes(self.value) 65 | 66 | def __len__(self): 67 | """Return the byte length of self.value.""" 68 | return len(bytes(self.value)) 69 | 70 | @abstractmethod 71 | def __repr__(self): 72 | pass 73 | 74 | def __str__(self): 75 | return "{}: ({}, {}, {})".format(self.name, self.key, len(self), self.value) 76 | 77 | 78 | class UnknownElement(Element): 79 | def __repr__(self): 80 | """Return as-code string used to re-create the object.""" 81 | args = ', '.join(map(repr, (bytes(self.key), bytes(self.value)))) 82 | return '{}({})'.format(self.__class__.__name__, args) 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /klvdata/misb0102.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2017 Matthew Pare (paretech@gmail.com) 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | from klvdata.element import UnknownElement 27 | from klvdata.elementparser import BytesElementParser 28 | from klvdata.misb0601 import UASLocalMetadataSet 29 | from klvdata.setparser import SetParser 30 | 31 | _classifying_country_coding = { 32 | b'\x01': 'ISO-3166 Two Letter', 33 | b'\x02': 'ISO-3166 Three Letter', 34 | b'\x03': 'FIPS 10-4 Two Letter', 35 | b'\x04': 'FIPS 10-4 Four Letter', 36 | b'\x05': 'ISO-3166 Numeric', 37 | b'\x06': '1059 Two Letter', 38 | b'\x07': '1059 Three Letter', 39 | b'\x08': 'Omitted Value', 40 | b'\x09': 'Omitted Value', 41 | b'\x0A': 'FIPS 10-4 Mixed', 42 | b'\x0B': 'ISO 3166 Mixed', 43 | b'\x0C': 'STANAG 1059 Mixed', 44 | b'\x0D': 'GENC Two Letter', 45 | b'\x0E': 'GENC Three Letter', 46 | b'\x0F': 'GENC Numeric', 47 | b'\x10': 'GENC Mixed', 48 | } 49 | 50 | 51 | _object_country_coding = { 52 | b'\x01': 'ISO-3166 Two Letter', 53 | b'\x02': 'ISO-3166 Three Letter', 54 | b'\x03': 'ISO-3166 Numeric', 55 | b'\x04': 'FIPS 10-4 Two Letter', 56 | b'\x05': 'FIPS 10-4 Four Letter', 57 | b'\x06': '1059 Two Letter', 58 | b'\x07': '1059 Three Letter', 59 | b'\x08': 'Omitted Value', 60 | b'\x09': 'Omitted Value', 61 | b'\x0A': 'Omitted Value', 62 | b'\x0B': 'Omitted Value', 63 | b'\x0C': 'Omitted Value', 64 | b'\x0D': 'GENC Two Letter', 65 | b'\x0E': 'GENC Three Letter', 66 | b'\x0F': 'GENC Numeric', 67 | b'\x40': 'GENC AdminSub', 68 | } 69 | 70 | 71 | class UnknownElement(UnknownElement): 72 | pass 73 | 74 | 75 | @UASLocalMetadataSet.add_parser 76 | class SecurityLocalMetadataSet(SetParser): 77 | """MISB ST0102 Security Metadata nested local set parser. 78 | 79 | The Security Metdata set comprise information needed to 80 | comply with CAPCO, DoD Information Security Program and 81 | other normatively referenced security directives. 82 | 83 | Must be a subclass of Element or duck type Element. 84 | """ 85 | key, name = b'\x30', "Security Local Metadata Set" 86 | parsers = {} 87 | 88 | _unknown_element = UnknownElement 89 | 90 | 91 | @SecurityLocalMetadataSet.add_parser 92 | class SecurityClassification(BytesElementParser): 93 | """MISB ST0102 Security Classification value interpretation parser. 94 | 95 | The Security Classification metadata element contains a value 96 | representing the entire security classification of the file in 97 | accordance with U.S. and NATO classification guidance. 98 | """ 99 | key = b'\x01' 100 | 101 | _classification = { 102 | b'\x01': 'UNCLASSIFIED', 103 | b'\x02': 'RESTRICTED', 104 | b'\x03': 'CONFIDENTIAL', 105 | b'\x04': 'SECRET', 106 | b'\x05': 'TOP SECRET', 107 | } 108 | -------------------------------------------------------------------------------- /klvdata/elementparser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2017 Matthew Pare (paretech@gmail.com) 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | from abc import ABCMeta 27 | from abc import abstractmethod 28 | from klvdata.element import Element 29 | from klvdata.common import bytes_to_datetime 30 | from klvdata.common import bytes_to_int 31 | from klvdata.common import bytes_to_float 32 | from klvdata.common import bytes_to_hexstr 33 | from klvdata.common import bytes_to_str 34 | from klvdata.common import datetime_to_bytes 35 | from klvdata.common import float_to_bytes 36 | from klvdata.common import str_to_bytes 37 | 38 | 39 | class ElementParser(Element, metaclass=ABCMeta): 40 | """Construct a Element Parser base class. 41 | 42 | Element Parsers are used to enforce the convention that all Element Parsers 43 | already know the key of the element they are constructing. 44 | 45 | Element Parser is a helper class that simplifies known element definition 46 | and makes a layer of abstraction for functionality that all known elements 47 | can share. The parsing interfaces are cleaner and require less coding as 48 | their definitions (subclasses of Element Parser) do not need to call init 49 | on super with class key and instance value. 50 | """ 51 | 52 | def __init__(self, value): 53 | super().__init__(self.key, value) 54 | 55 | @property 56 | @classmethod 57 | @abstractmethod 58 | def key(cls): 59 | pass 60 | 61 | def __repr__(self): 62 | """Return as-code string used to re-create the object.""" 63 | return '{}({})'.format(self.name, bytes(self.value)) 64 | 65 | 66 | class BaseValue(metaclass=ABCMeta): 67 | """Abstract base class (superclass) used to insure internal interfaces are maintained.""" 68 | @abstractmethod 69 | def __bytes__(self): 70 | """Required by element.Element""" 71 | pass 72 | 73 | @abstractmethod 74 | def __str__(self): 75 | """Required by element.Element""" 76 | pass 77 | 78 | 79 | class BytesElementParser(ElementParser, metaclass=ABCMeta): 80 | def __init__(self, value): 81 | super().__init__(BytesValue(value)) 82 | 83 | 84 | class BytesValue(BaseValue): 85 | def __init__(self, value): 86 | self.value = value 87 | 88 | def __bytes__(self): 89 | return bytes(self.value) 90 | 91 | def __str__(self): 92 | return bytes_to_hexstr(self.value, start='0x', sep='') 93 | 94 | 95 | class DateTimeElementParser(ElementParser, metaclass=ABCMeta): 96 | def __init__(self, value): 97 | super().__init__(DateTimeValue(value)) 98 | 99 | 100 | class DateTimeValue(BaseValue): 101 | def __init__(self, value): 102 | self.value = bytes_to_datetime(value) 103 | 104 | def __bytes__(self): 105 | return datetime_to_bytes(self.value) 106 | 107 | def __str__(self): 108 | return self.value.isoformat(sep=' ') 109 | 110 | 111 | class StringElementParser(ElementParser, metaclass=ABCMeta): 112 | def __init__(self, value): 113 | super().__init__(StringValue(value)) 114 | 115 | 116 | class StringValue(BaseValue): 117 | def __init__(self, value): 118 | try: 119 | self.value = bytes_to_str(value) 120 | except TypeError: 121 | self.value = value 122 | 123 | def __bytes__(self): 124 | return str_to_bytes(self.value) 125 | 126 | def __str__(self): 127 | return str(self.value) 128 | 129 | 130 | class MappedElementParser(ElementParser, metaclass=ABCMeta): 131 | def __init__(self, value): 132 | super().__init__(MappedValue(value, self._domain, self._range)) 133 | 134 | @property 135 | @classmethod 136 | @abstractmethod 137 | def _domain(cls): 138 | pass 139 | 140 | @property 141 | @classmethod 142 | @abstractmethod 143 | def _range(cls): 144 | pass 145 | 146 | class MappedValue(BaseValue): 147 | def __init__(self, value, _domain, _range): 148 | self._domain = _domain 149 | self._range = _range 150 | 151 | try: 152 | self.value = bytes_to_float(value, self._domain, self._range) 153 | except TypeError: 154 | self.value = value 155 | 156 | def __bytes__(self): 157 | return float_to_bytes(self.value, self._domain, self._range) 158 | 159 | def __str__(self): 160 | return format(self.value) 161 | 162 | def __float__(self): 163 | return self.value 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /klvdata/common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2017 Matthew Pare (paretech@gmail.com) 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | from struct import pack 27 | from struct import unpack 28 | from datetime import datetime 29 | from datetime import timezone 30 | from binascii import hexlify, unhexlify 31 | 32 | def datetime_to_bytes(value): 33 | """Return bytes representing UTC time in microseconds.""" 34 | return pack('>Q', int(value.timestamp() * 1e6)) 35 | 36 | 37 | def bytes_to_datetime(value): 38 | """Return datetime from microsecond bytes.""" 39 | return datetime.fromtimestamp(bytes_to_int(value)/1e6, tz=timezone.utc) 40 | 41 | 42 | def bytes_to_int(value, signed=False): 43 | """Return integer given bytes.""" 44 | return int.from_bytes(bytes(value), byteorder='big', signed=signed) 45 | 46 | 47 | def int_to_bytes(value, length=1, signed=False): 48 | """Return bytes given integer""" 49 | return int(value).to_bytes(length, byteorder='big', signed=signed) 50 | 51 | 52 | def ber_decode(value): 53 | """Return decoded BER length as integer given bytes.""" 54 | if bytes_to_int(value) < 128: 55 | if len(value) > 1: 56 | raise ValueError 57 | 58 | # Return BER Short Form 59 | return bytes_to_int(value) 60 | else: 61 | if len(value) != (value[0] - 127): 62 | raise ValueError 63 | 64 | # Return BER Long Form 65 | return bytes_to_int(value[1:]) 66 | 67 | 68 | def ber_encode(value): 69 | """Return encoded BER length as bytes given integer.""" 70 | if value < 128: 71 | # BER Short Form 72 | return int_to_bytes(value) 73 | else: 74 | # BER Long Form 75 | byte_length = ((value.bit_length() - 1) // 8) + 1 76 | 77 | return int_to_bytes(byte_length + 128) + int_to_bytes(value, length=byte_length) 78 | 79 | 80 | def bytes_to_str(value): 81 | """Return UTF-8 formatted string from bytes object.""" 82 | return bytes(value).decode('UTF-8') 83 | 84 | 85 | def str_to_bytes(value): 86 | """Return bytes object from UTF-8 formatted string.""" 87 | return bytes(str(value), 'UTF-8') 88 | 89 | 90 | def hexstr_to_bytes(value): 91 | """Return bytes object and filter out formatting characters from a string of hexadecimal numbers.""" 92 | return bytes.fromhex(''.join(filter(str.isalnum, value))) 93 | 94 | 95 | def bytes_to_hexstr(value, start='', sep=' '): 96 | """Return string of hexadecimal numbers separated by spaces from a bytes object.""" 97 | return start + sep.join(["{:02X}".format(byte) for byte in bytes(value)]) 98 | 99 | 100 | def linear_map(src_value, src_domain, dst_range): 101 | """Maps source value (src_value) in the source domain 102 | (source_domain) onto the destination range (dest_range) using linear 103 | interpretation. 104 | 105 | Except that at the moment src_value is a bytes value that once converted 106 | to integer that it then is on the src_domain. 107 | 108 | Ideally would like to move the conversion from bytes to int externally. 109 | 110 | Once value is same base and format as src_domain (i.e. converted from bytes), 111 | it should always fall within the src_domain. If not, that's a problem. 112 | """ 113 | src_min, src_max, dst_min, dst_max = src_domain + dst_range 114 | # assert(src_min <= src_value <= src_max) 115 | 116 | if not (src_min <= src_value <= src_max): 117 | return -1000 # ValueError 118 | 119 | slope = (dst_max - dst_min) / (src_max - src_min) 120 | dst_value = slope * (src_value - src_min) + dst_min 121 | 122 | if not (dst_min <= dst_value <= dst_max): 123 | return -2000 # ValueError 124 | 125 | return dst_value 126 | 127 | 128 | def bytes_to_float(value, _domain, _range): 129 | """Convert the fixed point value self.value to a floating point value.""" 130 | src_value = int().from_bytes(value, byteorder='big', signed=(min(_domain) < 0)) 131 | return linear_map(src_value, _domain, _range) 132 | 133 | 134 | def float_to_bytes(value, _domain, _range): 135 | """Convert the fixed point value self.value to a floating point value.""" 136 | # Some classes like MappedElement are calling float_to_bytes with arguments _domain 137 | # and _range in the incorrect order. The naming convention used is confusing and 138 | # needs addressed. Until that time, swap the order here as a workaround... 139 | src_domain, dst_range = _range, _domain 140 | src_min, src_max, dst_min, dst_max = src_domain + dst_range 141 | length = int((dst_max - dst_min - 1).bit_length() / 8) 142 | dst_value = linear_map(value, src_domain=src_domain, dst_range=dst_range) 143 | return round(dst_value).to_bytes(length, byteorder='big', signed=(dst_min < 0)) 144 | 145 | 146 | def packet_checksum(data): 147 | """Return two byte checksum from a SMPTE ST 336 KLV structured bytes object.""" 148 | length = len(data) - 2 149 | word_size, mod = divmod(length, 2) 150 | 151 | words = sum(unpack(">{:d}H".format(word_size), data[0:length - mod])) 152 | 153 | if mod: 154 | words += data[length - 1] << 8 155 | 156 | return pack('>H', words & 0xFFFF) 157 | -------------------------------------------------------------------------------- /klvdata/setparser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2017 Matthew Pare (paretech@gmail.com) 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | from pprint import pformat 27 | from abc import ABCMeta 28 | from abc import abstractmethod 29 | from collections import OrderedDict 30 | from klvdata.element import Element 31 | from klvdata.element import UnknownElement 32 | from klvdata.klvparser import KLVParser 33 | 34 | 35 | class SetParser(Element, metaclass=ABCMeta): 36 | """Parsable Element. Not intended to be used directly. Always as super class.""" 37 | _unknown_element = UnknownElement 38 | 39 | def __init__(self, value, key_length=1): 40 | """All parser needs is the value, no other information""" 41 | super().__init__(self.key, value) 42 | self.key_length = key_length 43 | self.items = OrderedDict() 44 | self.parse() 45 | 46 | def __getitem__(self, key): 47 | """Return element provided bytes key. 48 | 49 | For consistency of this collection of modules, __getitem__ does not 50 | attempt to add convenience of being able to index by the int equivalent. 51 | Instead, the user should pass keys with method bytes. 52 | """ 53 | return self.items[bytes(key)] 54 | 55 | def parse(self): 56 | """Parse the parent into items. Called on init and modification of parent value. 57 | 58 | If a known parser is not available for key, parse as generic KLV element. 59 | """ 60 | for key, value in KLVParser(self.value, self.key_length): 61 | try: 62 | self.items[key] = self.parsers[key](value) 63 | except KeyError: 64 | self.items[key] = self._unknown_element(key, value) 65 | except: 66 | pass # TODO do something clever 67 | 68 | @classmethod 69 | def add_parser(cls, obj): 70 | """Decorator method used to register a parser to the class parsing repertoire. 71 | 72 | obj is required to implement key attribute supporting bytes as returned by KLVParser key. 73 | """ 74 | 75 | # If sublcass of ElementParser does not implement key, dict accepts key of 76 | # type property object. bytes(obj.key) will raise TypeError. ElementParser 77 | # requires key as abstract property but no raise until instantiation which 78 | # does not occur because the value is never recalled and instantiated from 79 | # parsers. 80 | cls.parsers[bytes(obj.key)] = obj 81 | 82 | return obj 83 | 84 | @property 85 | @classmethod 86 | @abstractmethod 87 | def parsers(cls): 88 | # Property must define __getitem__ 89 | pass 90 | 91 | @parsers.setter 92 | @classmethod 93 | @abstractmethod 94 | def parsers(cls): 95 | # Property must define __setitem__ 96 | pass 97 | 98 | def __repr__(self): 99 | return pformat(self.items, indent=1) 100 | 101 | def __str__(self): 102 | return str_dict(self.items) 103 | 104 | def MetadataList(self): 105 | ''' Return metadata dictionary''' 106 | metadata = {} 107 | 108 | def repeat(items, indent=1): 109 | for item in items: 110 | try: 111 | metadata[item.TAG] = (item.LDSName, item.ESDName, item.UDSName, str(item.value.value)) 112 | except: 113 | None 114 | if hasattr(item, 'items'): 115 | repeat(item.items.values(), indent + 1) 116 | repeat(self.items.values()) 117 | return OrderedDict(metadata) 118 | 119 | def custom_repr(self, item): 120 | return f'{str(type(item))}: {item.value}' 121 | 122 | def structure(self): 123 | print(self.custom_repr(self)) 124 | 125 | def repeat(items, indent=1): 126 | for item in items: 127 | print(indent * "\t" + self.custom_repr(item)) 128 | if hasattr(item, 'items'): 129 | repeat(item.items.values(), indent+1) 130 | 131 | repeat(self.items.values()) 132 | 133 | def validate(self): 134 | try: 135 | data = b'\x06\x0e\x2b\x34\x02' + self.key + self.length + self.value 136 | bcc = 0 137 | size = len(data) - 2 138 | for i in range(size): 139 | bcc += data[i] << (8 * ((i + 1) % 2)) 140 | 141 | bcc = bcc & 0xffff 142 | bcc_value = f'0x{bcc:04X}' 143 | crc_value = str(self.items[b'\x01'].value) 144 | 145 | is_valid = crc_value == bcc_value 146 | print(f'Is valid: {is_valid}, Calculated CRC: {bcc_value}, CRC value: {crc_value}') 147 | except Exception as e: 148 | print(f'Cannot validate: {e}') 149 | 150 | 151 | def str_dict(values): 152 | out = [] 153 | 154 | def per_item(value, indent=0): 155 | for item in value: 156 | if isinstance(item): 157 | out.append(indent * "\t" + str(item)) 158 | else: 159 | out.append(indent * "\t" + str(item)) 160 | 161 | per_item(values) 162 | 163 | return '\n'.join(out) 164 | -------------------------------------------------------------------------------- /klvdata/misb0601.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | # The MIT License (MIT) 5 | # 6 | # Copyright (c) 2017 Matthew Pare (paretech@gmail.com) 7 | # 8 | # Permission is hereby granted, free of charge, to any person obtaining a copy 9 | # of this software and associated documentation files (the "Software"), to deal 10 | # in the Software without restriction, including without limitation the rights 11 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | # copies of the Software, and to permit persons to whom the Software is 13 | # furnished to do so, subject to the following conditions: 14 | # 15 | # The above copyright notice and this permission notice shall be included in all 16 | # copies or substantial portions of the Software. 17 | # 18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | # SOFTWARE. 25 | 26 | from klvdata.common import hexstr_to_bytes 27 | from klvdata.element import UnknownElement 28 | from klvdata.elementparser import BytesElementParser 29 | from klvdata.elementparser import DateTimeElementParser 30 | from klvdata.elementparser import MappedElementParser 31 | from klvdata.elementparser import StringElementParser 32 | from klvdata.setparser import SetParser 33 | from klvdata.streamparser import StreamParser 34 | 35 | 36 | class UnknownElement(UnknownElement): 37 | pass 38 | 39 | 40 | @StreamParser.add_parser 41 | class UASLocalMetadataSet(SetParser): 42 | """MISB ST0601 UAS Local Metadata Set 43 | """ 44 | # key = hexstr_to_bytes('06 0E 2B 34 - 02 0B 01 01 – 0E 01 03 01 - 01 00 00 00') 45 | key = hexstr_to_bytes('0B 01 01 – 0E 01 03 01 - 01 00 00 00') 46 | name = 'UAS Datalink Local Set' 47 | 48 | parsers = {} 49 | 50 | _unknown_element = UnknownElement 51 | 52 | 53 | @UASLocalMetadataSet.add_parser 54 | class Checksum(BytesElementParser): 55 | """Checksum used to detect errors within a UAV Local Set packet. 56 | 57 | Checksum formed as lower 16-bits of summation performed on entire 58 | LS packet, including 16-byte US key and 1-byte checksum length. 59 | 60 | Initialized from bytes value as BytesValue. 61 | """ 62 | key = b'\x01' 63 | TAG = 1 64 | UDSKey = "-" 65 | LDSName = "Checksum" 66 | ESDName = "" 67 | UDSName = "" 68 | 69 | 70 | @UASLocalMetadataSet.add_parser 71 | class PrecisionTimeStamp(DateTimeElementParser): 72 | """Precision Timestamp represented in microseconds. 73 | 74 | Precision Timestamp represented in the number of microseconds elapsed 75 | since midnight (00:00:00), January 1, 1970 not including leap seconds. 76 | 77 | See MISB ST 0601.11 for additional details. 78 | """ 79 | key = b'\x02' 80 | TAG = 2 81 | UDSKey = "06 0E 2B 34 01 01 01 03 07 02 01 01 01 05 00 00" 82 | LDSName = "Precision Time Stamp" 83 | ESDName = "" 84 | UDSName = "User Defined Time Stamp" 85 | 86 | 87 | @UASLocalMetadataSet.add_parser 88 | class MissionID(StringElementParser): 89 | """Mission ID is the descriptive mission identifier. 90 | 91 | Mission ID value field free text with maximum of 127 characters 92 | describing the event. 93 | """ 94 | key = b'\x03' 95 | TAG = 3 96 | UDSKey = "06 0E 2B 34 01 01 01 01 01 05 05 00 00 00 00 00" 97 | LDSName = "Mission ID" 98 | ESDName = "Mission Number" 99 | UDSName = "Episode Number" 100 | min_length, max_length = 0, 127 101 | 102 | 103 | @UASLocalMetadataSet.add_parser 104 | class PlatformTailNumber(StringElementParser): 105 | key = b'\x04' 106 | TAG = 4 107 | UDSKey = "-" 108 | LDSName = "Platform Tail Number" 109 | ESDName = "Platform Tail Number" 110 | UDSName = "" 111 | min_length, max_length = 0, 127 112 | 113 | 114 | @UASLocalMetadataSet.add_parser 115 | class PlatformHeadingAngle(MappedElementParser): 116 | key = b'\x05' 117 | TAG = 5 118 | UDSKey = "06 0E 2B 34 01 01 01 07 07 01 10 01 06 00 00 00" 119 | LDSName = "Platform Heading Angle" 120 | ESDName = "UAV Heading (INS)" 121 | UDSName = "Platform Heading Angle" 122 | _domain = (0, 2**16-1) 123 | _range = (0, 360) 124 | 125 | 126 | @UASLocalMetadataSet.add_parser 127 | class PlatformPitchAngle(MappedElementParser): 128 | key = b'\x06' 129 | TAG = 6 130 | UDSKey = "06 0E 2B 34 01 01 01 07 07 01 10 01 05 00 00 00" 131 | LDSName = "Platform Pitch Angle" 132 | ESDName = "UAV Pitch (INS)" 133 | UDSName = "Platform Pitch Angle" 134 | _domain = (-(2**15-1), 2**15-1) 135 | _range = (-20, 20) 136 | 137 | 138 | @UASLocalMetadataSet.add_parser 139 | class PlatformRollAngle(MappedElementParser): 140 | key = b'\x07' 141 | TAG = 7 142 | UDSKey = " 06 0E 2B 34 01 01 01 07 07 01 10 01 04 00 00 00" 143 | LDSName = "Platform Roll Angle" 144 | ESDName = "UAV Roll (INS)" 145 | UDSName = "Platform Roll Angle" 146 | _domain = (-(2**15-1), 2**15-1) 147 | _range = (-50, 50) 148 | units = 'degrees' 149 | 150 | 151 | @UASLocalMetadataSet.add_parser 152 | class PlatformTrueAirspeed(MappedElementParser): 153 | key = b'\x08' 154 | TAG = 8 155 | UDSKey = "-" 156 | LDSName = "Platform True Airspeed" 157 | ESDName = "True Airspeed" 158 | UDSName = "" 159 | _domain = (0, 2**8-1) 160 | _range = (0, 255) 161 | units = 'meters/second' 162 | 163 | 164 | @UASLocalMetadataSet.add_parser 165 | class PlatformIndicatedAirspeed(MappedElementParser): 166 | key = b'\x09' 167 | TAG = 9 168 | UDSKey = "-" 169 | LDSName = "Platform Indicated Airspeed" 170 | ESDName = "Indicated Airspeed" 171 | UDSName = "" 172 | _domain = (0, 2**8-1) 173 | _range = (0, 255) 174 | units = 'meters/second' 175 | 176 | 177 | @UASLocalMetadataSet.add_parser 178 | class PlatformDesignation(StringElementParser): 179 | key = b'\x0A' 180 | TAG = 10 181 | UDSKey = "06 0E 2B 34 01 01 01 01 01 01 20 01 00 00 00 00" 182 | LDSName = "Platform Designation" 183 | ESDName = "Project ID Code" 184 | UDSName = "Device Designation" 185 | min_length, max_length = 0, 127 186 | 187 | 188 | @UASLocalMetadataSet.add_parser 189 | class ImageSourceSensor(StringElementParser): 190 | key = b'\x0B' 191 | TAG = 11 192 | UDSKey = "06 0E 2B 34 01 01 01 01 04 20 01 02 01 01 00 00" 193 | LDSName = "Image Source Sensor" 194 | ESDName = "Sensor Name" 195 | UDSName = "Image Source Device" 196 | min_length, max_length = 0, 127 197 | 198 | 199 | @UASLocalMetadataSet.add_parser 200 | class ImageCoordinateSystem(StringElementParser): 201 | key = b'\x0C' 202 | TAG = 12 203 | UDSKey = "06 0E 2B 34 01 01 01 01 07 01 01 01 00 00 00 00" 204 | LDSName = "Image Coordinate System" 205 | ESDName = "Image Coordinate System" 206 | UDSName = "Image Coordinate System" 207 | min_length, max_length = 0, 127 208 | 209 | 210 | @UASLocalMetadataSet.add_parser 211 | class SensorLatitude(MappedElementParser): 212 | key = b'\x0D' 213 | TAG = 13 214 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 02 04 02 00" 215 | LDSName = "Sensor Latitude" 216 | ESDName = "Sensor Latitude" 217 | UDSName = "Device Latitude" 218 | _domain = (-(2**31-1), 2**31-1) 219 | _range = (-90, 90) 220 | units = 'degrees' 221 | 222 | 223 | @UASLocalMetadataSet.add_parser 224 | class SensorLongitude(MappedElementParser): 225 | key = b'\x0E' 226 | TAG = 14 227 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 02 06 02 00" 228 | LDSName = "Sensor Longitude" 229 | ESDName = "Sensor Longitude" 230 | UDSName = "Device Longitude" 231 | _domain = (-(2**31-1), 2**31-1) 232 | _range = (-180, 180) 233 | units = 'degrees' 234 | 235 | 236 | @UASLocalMetadataSet.add_parser 237 | class SensorTrueAltitude(MappedElementParser): 238 | key = b'\x0F' 239 | TAG = 15 240 | UDSKey = "06 0E 2B 34 01 01 01 01 07 01 02 01 02 02 00 00" 241 | LDSName = "Sensor True Altitude" 242 | ESDName = "Sensor Altitude" 243 | UDSName = "Device Altitude" 244 | _domain = (0, 2**16-1) 245 | _range = (-900, 19000) 246 | units = 'meters' 247 | 248 | 249 | @UASLocalMetadataSet.add_parser 250 | class SensorHorizontalFieldOfView(MappedElementParser): 251 | key = b'\x10' 252 | TAG = 16 253 | UDSKey = "06 0E 2B 34 01 01 01 02 04 20 02 01 01 08 00 00" 254 | LDSName = "Sensor Horizontal Field of View" 255 | ESDName = "Field of View" 256 | UDSName = "Field of View (FOVHorizontal)" 257 | _domain = (0, 2**16-1) 258 | _range = (0, 180) 259 | units = 'degrees' 260 | 261 | 262 | @UASLocalMetadataSet.add_parser 263 | class SensorVerticalFieldOfView(MappedElementParser): 264 | key = b'\x11' 265 | TAG = 17 266 | UDSKey = "-" 267 | LDSName = "Sensor Vertical Field of View" 268 | ESDName = "Vertical Field of View" 269 | UDSName = "" 270 | _domain = (0, 2**16-1) 271 | _range = (0, 180) 272 | units = 'degrees' 273 | 274 | 275 | @UASLocalMetadataSet.add_parser 276 | class SensorRelativeAzimuthAngle(MappedElementParser): 277 | key = b'\x12' 278 | TAG = 18 279 | UDSKey = "-" 280 | LDSName = "Sensor Relative Azimuth Angle" 281 | ESDName = "Sensor Relative Azimuth Angle" 282 | UDSName = "" 283 | _domain = (0, 2**32-1) 284 | _range = (0, 360) 285 | units = 'degrees' 286 | 287 | 288 | @UASLocalMetadataSet.add_parser 289 | class SensorRelativeElevationAngle(MappedElementParser): 290 | key = b'\x13' 291 | TAG = 19 292 | UDSKey = "-" 293 | LDSName = "Sensor Relative Elevation Angle" 294 | ESDName = "Sensor Relative Elevation Angle" 295 | UDSName = "" 296 | _domain = (-(2**31 - 1), 2**31 - 1) 297 | _range = (-180, 180) 298 | units = 'degrees' 299 | 300 | 301 | @UASLocalMetadataSet.add_parser 302 | class SensorRelativeRollAngle(MappedElementParser): 303 | key = b'\x14' 304 | TAG = 20 305 | UDSKey = "-" 306 | LDSName = "Sensor Relative Roll Angle" 307 | ESDName = "Sensor Relative Roll Angle" 308 | UDSName = "" 309 | _domain = (0, 2**32-1) 310 | _range = (0, 360) 311 | units = 'degrees' 312 | 313 | 314 | @UASLocalMetadataSet.add_parser 315 | class SlantRange(MappedElementParser): 316 | key = b'\x15' 317 | TAG = 21 318 | UDSKey = "06 0E 2B 34 01 01 01 01 07 01 08 01 01 00 00 00" 319 | LDSName = "Slant Range" 320 | ESDName = "Slant Range" 321 | UDSName = "Slant Range" 322 | _domain = (0, 2**32-1) 323 | _range = (0, +5e6) 324 | units = 'meters' 325 | 326 | 327 | @UASLocalMetadataSet.add_parser 328 | class TargetWidth(MappedElementParser): 329 | key = b'\x16' 330 | TAG = 22 331 | UDSKey = "06 0E 2B 34 01 01 01 01 07 01 09 02 01 00 00 00" 332 | LDSName = "Target Width" 333 | ESDName = "Target Width" 334 | UDSName = "Target Width" 335 | _domain = (0, 2**16-1) 336 | _range = (0, +10e3) 337 | units = 'meters' 338 | 339 | 340 | @UASLocalMetadataSet.add_parser 341 | class FrameCenterLatitude(MappedElementParser): 342 | key = b'\x17' 343 | TAG = 23 344 | UDSKey = "06 0E 2B 34 01 01 01 01 07 01 02 01 03 02 00 00" 345 | LDSName = "Frame Center Latitude" 346 | ESDName = "Target Latitude" 347 | UDSName = "Frame Center Latitude" 348 | _domain = (-(2**31 - 1), 2**31 - 1) 349 | _range = (-90, 90) 350 | units = 'degrees' 351 | 352 | 353 | @UASLocalMetadataSet.add_parser 354 | class FrameCenterLongitude(MappedElementParser): 355 | key = b'\x18' 356 | TAG = 24 357 | UDSKey = "06 0E 2B 34 01 01 01 01 07 01 02 01 03 04 00 00" 358 | LDSName = "Frame Center Longitude" 359 | ESDName = "Target Longitude" 360 | UDSName = "Frame Center Longitude" 361 | _domain = (-(2**31 - 1), 2**31 - 1) 362 | _range = (-180, 180) 363 | units = 'degrees' 364 | 365 | 366 | @UASLocalMetadataSet.add_parser 367 | class FrameCenterElevation(MappedElementParser): 368 | key = b'\x19' 369 | TAG = 25 370 | UDSKey = "-" 371 | LDSName = "Frame Center Elevation" 372 | ESDName = "Frame Center Elevation" 373 | UDSName = "" 374 | _domain = (0, 2**16-1) 375 | _range = (-900, +19e3) 376 | units = 'meters' 377 | 378 | 379 | @UASLocalMetadataSet.add_parser 380 | class OffsetCornerLatitudePoint1(MappedElementParser): 381 | key = b'\x1A' 382 | TAG = 26 383 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 07 01 00" 384 | LDSName = "Offset Corner Latitude Point 1" 385 | ESDName = "SAR Latitude 4" 386 | UDSName = "Corner Latitude Point 1" 387 | _domain = (-(2**15 - 1), 2**15 - 1) 388 | _range = (-0.075, +0.075) 389 | units = 'degrees' 390 | 391 | 392 | @UASLocalMetadataSet.add_parser 393 | class OffsetCornerLongitudePoint1(MappedElementParser): 394 | key = b'\x1B' 395 | TAG = 27 396 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 0B 01 00" 397 | LDSName = "Offset Corner Longitude Point 1" 398 | ESDName = "SAR Longitude 4" 399 | UDSName = "Corner Longitude Point 1" 400 | _domain = (-(2**15 - 1), 2**15 - 1) 401 | _range = (-0.075, 0.075) 402 | units = 'degrees' 403 | 404 | 405 | @UASLocalMetadataSet.add_parser 406 | class OffsetCornerLatitudePoint2(MappedElementParser): 407 | key = b'\x1C' 408 | TAG = 28 409 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 08 01 00" 410 | LDSName = "Offset Corner Latitude Point 2" 411 | ESDName = "SAR Latitude 1" 412 | UDSName = "Corner Latitude Point 2" 413 | _domain = (-(2**15 - 1), 2**15 - 1) 414 | _range = (-0.075, 0.075) 415 | units = 'degrees' 416 | 417 | 418 | @UASLocalMetadataSet.add_parser 419 | class OffsetCornerLongitudePoint2(MappedElementParser): 420 | key = b'\x1D' 421 | TAG = 29 422 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 0C 01 00" 423 | LDSName = "Offset Corner Longitude Point 2" 424 | ESDName = "SAR Longitude 1" 425 | UDSName = "Corner Longitude Point 2" 426 | _domain = (-(2**15 - 1), 2**15 - 1) 427 | _range = (-0.075, 0.075) 428 | units = 'degrees' 429 | 430 | 431 | @UASLocalMetadataSet.add_parser 432 | class OffsetCornerLatitudePoint3(MappedElementParser): 433 | key = b'\x1E' 434 | TAG = 30 435 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 09 01 00" 436 | LDSName = "Offset Corner Latitude Point 3" 437 | ESDName = "SAR Latitude 2" 438 | UDSName = "Corner Latitude Point 3" 439 | _domain = (-(2**15 - 1), 2**15 - 1) 440 | _range = (-0.075, 0.075) 441 | units = 'degrees' 442 | 443 | 444 | @UASLocalMetadataSet.add_parser 445 | class OffsetCornerLongitudePoint3(MappedElementParser): 446 | key = b'\x1F' 447 | TAG = 31 448 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 0D 01 00" 449 | LDSName = "Offset Corner Longitude Point 3" 450 | ESDName = "SAR Longitude 2" 451 | UDSName = "Corner Longitude Point 3" 452 | _domain = (-(2**15 - 1), 2**15 - 1) 453 | _range = (-0.075, 0.075) 454 | units = 'degrees' 455 | 456 | 457 | @UASLocalMetadataSet.add_parser 458 | class OffsetCornerLatitudePoint4(MappedElementParser): 459 | key = b'\x20' 460 | TAG = 32 461 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 0A 01 00" 462 | LDSName = "Offset Corner Latitude Point 4" 463 | ESDName = "SAR Latitude 3" 464 | UDSName = "Corner Latitude Point 4" 465 | _domain = (-(2**15 - 1), 2**15 - 1) 466 | _range = (-0.075, 0.075) 467 | units = 'degrees' 468 | 469 | 470 | @UASLocalMetadataSet.add_parser 471 | class OffsetCornerLongitudePoint4(MappedElementParser): 472 | key = b'\x21' 473 | TAG = 33 474 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 0E 01 00" 475 | LDSName = "Offset Corner Longitude Point 4" 476 | ESDName = "SAR Longitude 3" 477 | UDSName = "Corner Longitude Point 4" 478 | _domain = (-(2**15 - 1), 2**15 - 1) 479 | _range = (-0.075, 0.075) 480 | units = 'degrees' 481 | 482 | 483 | @UASLocalMetadataSet.add_parser 484 | class IcingDetected(MappedElementParser): 485 | key = b'\x22' 486 | TAG = 34 487 | UDSKey = "" 488 | LDSName = "Icing Detected" 489 | ESDName = "Icing Detected" 490 | UDSName = "" 491 | _domain = (0, 2**8-1) 492 | _range = (0, 2**8-1) 493 | units = 'flag' 494 | 495 | 496 | @UASLocalMetadataSet.add_parser 497 | class WindDirection(MappedElementParser): 498 | key = b'\x23' 499 | TAG = 35 500 | UDSKey = "-" 501 | LDSName = "Wind Direction" 502 | ESDName = "Wind Direction" 503 | UDSName = "" 504 | _domain = (0, 2**16 - 1) 505 | _range = (0, +360) 506 | units = 'meters/second' 507 | 508 | 509 | @UASLocalMetadataSet.add_parser 510 | class WindSpeed(MappedElementParser): 511 | key = b'\x24' 512 | TAG = 36 513 | UDSKey = "-" 514 | LDSName = "Wind Speed" 515 | ESDName = "Wind Speed" 516 | UDSName = "" 517 | _domain = (0, 255) 518 | _range = (0, +100) 519 | units = 'meters/second' 520 | 521 | 522 | @UASLocalMetadataSet.add_parser 523 | class StaticPressure(MappedElementParser): 524 | key = b'\x25' 525 | TAG = 37 526 | UDSKey = "-" 527 | LDSName = "Static Pressure" 528 | ESDName = "Static Pressure" 529 | UDSName = "" 530 | _domain = (0, 2**16 - 1) 531 | _range = (0, +5000) 532 | units = 'millibar' 533 | 534 | 535 | @UASLocalMetadataSet.add_parser 536 | class DensityAltitude(MappedElementParser): 537 | key = b'\x26' 538 | TAG = 38 539 | UDSKey = "-" 540 | LDSName = "Density Altitude" 541 | ESDName = "Density Altitude" 542 | UDSName = "" 543 | _domain = (0, 2**16 - 1) 544 | _range = (-900, +19e3) 545 | units = 'meters' 546 | 547 | 548 | @UASLocalMetadataSet.add_parser 549 | class OutsideAirTemperature(MappedElementParser): 550 | key = b'\x27' 551 | TAG = 39 552 | UDSKey = "-" 553 | LDSName = "Outside Air Temperature" 554 | ESDName = "Air Temperature" 555 | UDSName = "" 556 | _domain = (0, 2**8-1) 557 | _range = (0, 2**8-1) 558 | units = 'celcius' 559 | 560 | 561 | @UASLocalMetadataSet.add_parser 562 | class TargetLocationLatitude(MappedElementParser): 563 | key = b'\x28' 564 | TAG = 40 565 | UDSKey = "-" 566 | LDSName = "Target Location Latitude" 567 | ESDName = "" 568 | UDSName = "" 569 | _domain = (-(2**31 - 1), 2**31 - 1) 570 | _range = (-90, 90) 571 | units = 'degrees' 572 | 573 | 574 | @UASLocalMetadataSet.add_parser 575 | class TargetLocationLongitude(MappedElementParser): 576 | key = b'\x29' 577 | TAG = 41 578 | UDSKey = "-" 579 | LDSName = "Target Location Longitude" 580 | ESDName = "" 581 | UDSName = "" 582 | _domain = (-(2**31 - 1), 2**31 - 1) 583 | _range = (-180, 180) 584 | units = 'degrees' 585 | 586 | 587 | @UASLocalMetadataSet.add_parser 588 | class TargetLocationElevation(MappedElementParser): 589 | key = b'\x2A' 590 | TAG = 42 591 | UDSKey = "-" 592 | LDSName = "Target Location Elevation" 593 | ESDName = "" 594 | UDSName = "" 595 | _domain = (0, 2**16-1) 596 | _range = (-900, 19000) 597 | units = 'meters' 598 | 599 | 600 | @UASLocalMetadataSet.add_parser 601 | class TargetTrackGateWidth(MappedElementParser): 602 | key = b'\x2B' 603 | TAG = 43 604 | UDSKey = "-" 605 | LDSName = "Target Track Gate Width" 606 | ESDName = "" 607 | UDSName = "" 608 | _domain = (0, 2**8-1) 609 | _range = (0, 512) 610 | units = 'pixels' 611 | 612 | 613 | @UASLocalMetadataSet.add_parser 614 | class TargetTrackGateHeight(MappedElementParser): 615 | key = b'\x2C' 616 | TAG = 44 617 | UDSKey = "-" 618 | LDSName = "Target Track Gate Height" 619 | ESDName = "" 620 | UDSName = "" 621 | _domain = (0, 2**8-1) 622 | _range = (0, 512) 623 | units = 'pixels' 624 | 625 | 626 | @UASLocalMetadataSet.add_parser 627 | class TargetErrorEstimateCE90(MappedElementParser): 628 | key = b'\x2D' 629 | TAG = 45 630 | UDSKey = "-" 631 | LDSName = "Target Error Estimate - CE90" 632 | ESDName = "" 633 | UDSName = "" 634 | _domain = (0, 2**16-1) 635 | _range = (0, 4095) 636 | units = 'meters' 637 | 638 | 639 | @UASLocalMetadataSet.add_parser 640 | class TargetErrorEstimateLE90(MappedElementParser): 641 | key = b'\x2E' 642 | TAG = 46 643 | UDSKey = "-" 644 | LDSName = "Target Error Estimate - LE90" 645 | ESDName = "" 646 | UDSName = "" 647 | _domain = (0, 2**16-1) 648 | _range = (0, 4095) 649 | units = 'meters' 650 | 651 | 652 | @UASLocalMetadataSet.add_parser 653 | class GenericFlagData01(MappedElementParser): 654 | key = b'\x2F' 655 | TAG = 47 656 | UDSKey = "-" 657 | LDSName = "Generic Flag Data 01" 658 | ESDName = "" 659 | UDSName = "" 660 | _domain = (0, 2**8-1) 661 | _range = (0, 2**8-1) 662 | 663 | 664 | # @UASLocalMetadataSet.add_parser 665 | # class SecurityLocalMetadataSet(MappedElementParser): 666 | # key = b'\x30' 667 | # TAG = 48 668 | # UDSKey = "06 0E 2B 34 02 03 01 01 0E 01 03 03 02 00 00 00" 669 | # LDSName = "Security Local Set" 670 | # ESDName = "" 671 | # UDSName = "Security Local Set" 672 | 673 | 674 | @UASLocalMetadataSet.add_parser 675 | class DifferentialPressure(MappedElementParser): 676 | key = b'\x31' 677 | TAG = 49 678 | UDSKey = "-" 679 | LDSName = "Differential Pressure" 680 | ESDName = "" 681 | UDSName = "" 682 | _domain = (0, 2**16-1) 683 | _range = (0, 5000) 684 | units = 'millibar' 685 | 686 | 687 | @UASLocalMetadataSet.add_parser 688 | class PlatformAngleOfAttack(MappedElementParser): 689 | key = b'\x32' 690 | TAG = 50 691 | UDSKey = "-" 692 | LDSName = "Platform Angle of Attack" 693 | ESDName = "" 694 | UDSName = "" 695 | _domain = (-(2**15 - 1), 2**15 - 1) 696 | _range = (-20, 20) 697 | units = 'degrees' 698 | 699 | 700 | @UASLocalMetadataSet.add_parser 701 | class PlatformVerticalSpeed(MappedElementParser): 702 | key = b'\x33' 703 | TAG = 51 704 | UDSKey = "-" 705 | LDSName = "Platform Vertical Speed" 706 | ESDName = "" 707 | UDSName = "" 708 | _domain = (-(2**15 - 1), 2**15 - 1) 709 | _range = (-180, 180) 710 | units = 'meters/second' 711 | 712 | 713 | @UASLocalMetadataSet.add_parser 714 | class PlatformSideslipAngle(MappedElementParser): 715 | key = b'\x34' 716 | TAG = 52 717 | UDSKey = "-" 718 | LDSName = "Platform Sideslip Angle" 719 | ESDName = "" 720 | UDSName = "" 721 | _domain = (-(2**15 - 1), 2**15 - 1) 722 | _range = (-20, 20) 723 | units = 'degrees' 724 | 725 | 726 | @UASLocalMetadataSet.add_parser 727 | class AirfieldBarometricPressure(MappedElementParser): 728 | key = b'\x35' 729 | TAG = 53 730 | UDSKey = "-" 731 | LDSName = "Airfield Barometric Pressure" 732 | ESDName = "" 733 | UDSName = "" 734 | _domain = (0, 2**16-1) 735 | _range = (0, 5000) 736 | units = 'millibar' 737 | 738 | 739 | @UASLocalMetadataSet.add_parser 740 | class AirfieldElevation(MappedElementParser): 741 | key = b'\x36' 742 | TAG = 54 743 | UDSKey = "-" 744 | LDSName = "Airfield Elevation" 745 | ESDName = "" 746 | UDSName = "" 747 | _domain = (0, 2**16-1) 748 | _range = (-900, 19000) 749 | units = 'meters' 750 | 751 | 752 | @UASLocalMetadataSet.add_parser 753 | class RelativeHumidity(MappedElementParser): 754 | key = b'\x37' 755 | TAG = 55 756 | UDSKey = "-" 757 | LDSName = "Relative Humidity" 758 | ESDName = "" 759 | UDSName = "" 760 | _domain = (0, 2**8-1) 761 | _range = (0, 100) 762 | units = '%' 763 | 764 | 765 | @UASLocalMetadataSet.add_parser 766 | class PlatformGroundSpeed(MappedElementParser): 767 | key = b'\x38' 768 | TAG = 56 769 | UDSKey = "-" 770 | LDSName = "Platform Ground Speed" 771 | ESDName = "Platform Ground Speed" 772 | UDSName = "" 773 | _domain = (0, 2**8-1) 774 | _range = (0, 255) 775 | units = 'meters/second' 776 | 777 | 778 | @UASLocalMetadataSet.add_parser 779 | class GroundRange(MappedElementParser): 780 | key = b'\x39' 781 | TAG = 57 782 | UDSKey = "-" 783 | LDSName = "Ground Range" 784 | ESDName = "Ground Range" 785 | UDSName = "" 786 | _domain = (0, 2**32-1) 787 | _range = (0, 5000000) 788 | units = 'meters' 789 | 790 | 791 | @UASLocalMetadataSet.add_parser 792 | class PlatformFuelRemaining(MappedElementParser): 793 | key = b'\x3A' 794 | TAG = 58 795 | UDSKey = "-" 796 | LDSName = "Platform Fuel Remaining" 797 | ESDName = "Platform Fuel Remaining" 798 | UDSName = "" 799 | _domain = (0, 2**16-1) 800 | _range = (0, 10000) 801 | units = 'kilograms' 802 | 803 | 804 | @UASLocalMetadataSet.add_parser 805 | class PlatformCallSign(StringElementParser): 806 | key = b'\x3B' 807 | TAG = 59 808 | UDSKey = "-" 809 | LDSName = "Platform Call Sign" 810 | ESDName = "Platform Call Sign" 811 | UDSName = "" 812 | 813 | 814 | @UASLocalMetadataSet.add_parser 815 | class WeaponLoad(MappedElementParser): 816 | key = b'\x3C' 817 | TAG = 60 818 | UDSKey = "-" 819 | LDSName = "Weapon Load" 820 | ESDName = "Weapon Load" 821 | UDSName = "" 822 | _domain = (0, 2**16-1) 823 | _range = (0, 2**16-1) 824 | 825 | @UASLocalMetadataSet.add_parser 826 | class WeaponFired(MappedElementParser): 827 | key = b'\x3D' 828 | TAG = 61 829 | UDSKey = "-" 830 | LDSName = "Weapon Fired" 831 | ESDName = "Weapon Fired" 832 | UDSName = "" 833 | _domain = (0, 2**8-1) 834 | _range = (0, 2**8-1) 835 | 836 | 837 | @UASLocalMetadataSet.add_parser 838 | class LaserPRFCode(MappedElementParser): 839 | key = b'\x3E' 840 | TAG = 62 841 | UDSKey = "-" 842 | LDSName = "Laser PRF Code" 843 | ESDName = "Laser PRF Code" 844 | UDSName = "" 845 | _domain = (0, 2**16-1) 846 | _range = (0, 65535) 847 | 848 | @UASLocalMetadataSet.add_parser 849 | class SensorFieldOfViewName(MappedElementParser): 850 | key = b'\x3F' 851 | TAG = 63 852 | UDSKey = "-" 853 | LDSName = "Sensor Field of View Name" 854 | ESDName = "Sensor Field of View Name" 855 | UDSName = "" 856 | _domain = (0, 2**8-1) 857 | _range = (0, 2**8-1) 858 | 859 | @UASLocalMetadataSet.add_parser 860 | class PlatformMagneticHeading(MappedElementParser): 861 | key = b'\x40' 862 | TAG = 64 863 | UDSKey = "-" 864 | LDSName = "Platform Magnetic Heading" 865 | ESDName = "Platform Magnetic Heading" 866 | UDSName = "" 867 | _domain = (0, 2**16-1) 868 | _range = (0, 360) 869 | units = 'degrees' 870 | 871 | 872 | @UASLocalMetadataSet.add_parser 873 | class UASLSVersionNumber(MappedElementParser): 874 | key = b'\x41' 875 | TAG = 65 876 | UDSKey = "-" 877 | LDSName = "UAS Datalink LS Version Number" 878 | ESDName = "ESD ICD Version" 879 | UDSName = "" 880 | _domain = (0, 2**8-1) 881 | _range = (0, 2**8-1) 882 | units = 'number' 883 | 884 | 885 | @UASLocalMetadataSet.add_parser 886 | class AlternatePlatformLatitude(MappedElementParser): 887 | key = b'\x43' 888 | TAG = 67 889 | UDSKey = "-" 890 | LDSName = "Alternate Platform Latitude" 891 | ESDName = "" 892 | UDSName = "" 893 | _domain = (-(2**31 - 1), 2**31 - 1) 894 | _range = (-90, 90) 895 | units = 'degrees' 896 | 897 | 898 | @UASLocalMetadataSet.add_parser 899 | class AlternatePlatformLongitude(MappedElementParser): 900 | key = b'\x44' 901 | TAG = 68 902 | UDSKey = "-" 903 | LDSName = "Alternate Platform Longitude" 904 | ESDName = "" 905 | UDSName = "" 906 | _domain = (-(2**31 - 1), 2**31 - 1) 907 | _range = (-180, 180) 908 | units = 'degrees' 909 | 910 | 911 | @UASLocalMetadataSet.add_parser 912 | class AlternatePlatformAltitude(MappedElementParser): 913 | key = b'\x45' 914 | TAG = 69 915 | UDSKey = "-" 916 | LDSName = "Alternate Platform Altitude" 917 | ESDName = "" 918 | UDSName = "" 919 | _domain = (0, 2**16 - 1) 920 | _range = (-900, 19000) 921 | units = 'meters' 922 | 923 | 924 | @UASLocalMetadataSet.add_parser 925 | class AlternatePlatformName(StringElementParser): 926 | key = b'\x46' 927 | TAG = 70 928 | UDSKey = "-" 929 | LDSName = "Alternate Platform Name" 930 | ESDName = "" 931 | UDSName = "" 932 | min_length, max_length = 0, 127 933 | 934 | 935 | @UASLocalMetadataSet.add_parser 936 | class AlternatePlatformHeading(MappedElementParser): 937 | key = b'\x47' 938 | TAG = 71 939 | UDSKey = "-" 940 | LDSName = "Alternate Platform Heading" 941 | ESDName = "" 942 | UDSName = "" 943 | _domain = (0, 2**16 - 1) 944 | _range = (0, 360) 945 | units = 'degrees' 946 | 947 | 948 | @UASLocalMetadataSet.add_parser 949 | class EventStartTime(DateTimeElementParser): 950 | key = b'\x48' 951 | TAG = 72 952 | UDSKey = "06 0E 2B 34 01 01 01 01 07 02 01 02 07 01 00 00" 953 | LDSName = "Event Start Time - UTC" 954 | ESDName = "Mission Start Time, Date, and Date of Collection" 955 | UDSName = "Event Start Date Time - UTC" 956 | 957 | 958 | @UASLocalMetadataSet.add_parser 959 | class RVTLocalSet(MappedElementParser): 960 | key = b'\x49' 961 | TAG = 73 962 | UDSKey = "06 0E 2B 34 01 01 01 01 07 02 01 02 07 01 00 00" 963 | LDSName = "RVT Local Data Set" 964 | ESDName = "" 965 | UDSName = "Remote Video Terminal Local Set" 966 | 967 | 968 | @UASLocalMetadataSet.add_parser 969 | class VMTILocalSet(MappedElementParser): 970 | key = b'\x4A' 971 | TAG = 74 972 | UDSKey = "06 0E 2B 34 02 0B 01 01 0E 01 03 03 06 00 00 00" 973 | LDSName = "VMTI Local Set" 974 | ESDName = "" 975 | UDSName = "Video Moving Target Indicator Local Set" 976 | 977 | 978 | @UASLocalMetadataSet.add_parser 979 | class SensorEllipsoidHeightConversion(MappedElementParser): 980 | key = b'\x4B' 981 | TAG = 75 982 | UDSKey = "-" 983 | LDSName = "Sensor Ellipsoid Height" 984 | ESDName = "" 985 | UDSName = "" 986 | _domain = (0, 2**16-1) 987 | _range = (-900, 19000) 988 | units = 'meters' 989 | 990 | 991 | @UASLocalMetadataSet.add_parser 992 | class AlternatePlatformEllipsoidHeight(MappedElementParser): 993 | key = b'\x4C' 994 | TAG = 76 995 | UDSKey = "-" 996 | LDSName = "Alternate Platform Ellipsoid Height" 997 | ESDName = "" 998 | UDSName = "" 999 | _domain = (0, 2**16-1) 1000 | _range = (-900, 19000) 1001 | units = 'meters' 1002 | 1003 | 1004 | @UASLocalMetadataSet.add_parser 1005 | class OperationalMode(StringElementParser): 1006 | key = b'\x4D' 1007 | TAG = 77 1008 | UDSKey = "-" 1009 | LDSName = "Operational Mode" 1010 | ESDName = "" 1011 | UDSName = "" 1012 | 1013 | 1014 | @UASLocalMetadataSet.add_parser 1015 | class FrameCenterHeightAboveEllipsoid(MappedElementParser): 1016 | key = b'\x4E' 1017 | TAG = 78 1018 | UDSKey = "-" 1019 | LDSName = "Frame Center Height Above Ellipsoid" 1020 | ESDName = "" 1021 | UDSName = "" 1022 | _domain = (0, 2**16-1) 1023 | _range = (-900, 19000) 1024 | units = 'meters' 1025 | 1026 | 1027 | @UASLocalMetadataSet.add_parser 1028 | class SensorNorthVelocity(MappedElementParser): 1029 | key = b'\x4F' 1030 | TAG = 79 1031 | UDSKey = "-" 1032 | LDSName = "Sensor North Velocity" 1033 | ESDName = "" 1034 | UDSName = "" 1035 | _domain = (-(2**15 - 1), 2**15 - 1) 1036 | _range = (-327, 327) 1037 | units = 'meters/second' 1038 | 1039 | 1040 | @UASLocalMetadataSet.add_parser 1041 | class SensorEastVelocity(MappedElementParser): 1042 | key = b'\x50' 1043 | TAG = 80 1044 | UDSKey = "-" 1045 | LDSName = "Sensor East Velocity" 1046 | ESDName = "" 1047 | UDSName = "" 1048 | _domain = (-(2**15 - 1), 2**15 - 1) 1049 | _range = (-327, 327) 1050 | units = 'meters/second' 1051 | 1052 | # @UASLocalMetadataSet.add_parser 1053 | # class ImageHorizonPixelPack(MappedElementParser): 1054 | # key = b'\x51' 1055 | # TAG = 81 1056 | # UDSKey = "-" 1057 | # LDSName = "Image Horizon Pixel Pack" 1058 | # ESDName = "" 1059 | # UDSName = "" 1060 | 1061 | 1062 | @UASLocalMetadataSet.add_parser 1063 | class CornerLatitudePoint1Full(MappedElementParser): 1064 | key = b'\x52' 1065 | TAG = 82 1066 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 07 01 00" 1067 | LDSName = "Corner Latitude Point 1 (Full)" 1068 | ESDName = "SAR Latitude 4" 1069 | UDSName = "Corner Latitude Point 1 (Decimal Degrees)" 1070 | _domain = (-(2**31 - 1), 2**31 - 1) 1071 | _range = (-90, 90) 1072 | units = 'degrees' 1073 | 1074 | 1075 | @UASLocalMetadataSet.add_parser 1076 | class CornerLongitudePoint1Full(MappedElementParser): 1077 | key = b'\x53' 1078 | TAG = 83 1079 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 0B 01 00" 1080 | LDSName = "Corner Longitude Point 1 (Full)" 1081 | ESDName = "SAR Longitude 4" 1082 | UDSName = "Corner Longitude Point 1 (Decimal Degrees)" 1083 | _domain = (-(2**31 - 1), 2**31 - 1) 1084 | _range = (-180, 180) 1085 | units = 'degrees' 1086 | 1087 | 1088 | @UASLocalMetadataSet.add_parser 1089 | class CornerLatitudePoint2Full(MappedElementParser): 1090 | key = b'\x54' 1091 | TAG = 84 1092 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 08 01 00" 1093 | LDSName = "Corner Latitude Point 2 (Full)" 1094 | ESDName = "SAR Latitude 1" 1095 | UDSName = "Corner Latitude Point 2 (Decimal Degrees)" 1096 | _domain = (-(2**31 - 1), 2**31 - 1) 1097 | _range = (-90, 90) 1098 | units = 'degrees' 1099 | 1100 | 1101 | @UASLocalMetadataSet.add_parser 1102 | class CornerLongitudePoint2Full(MappedElementParser): 1103 | key = b'\x55' 1104 | TAG = 85 1105 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 0C 01 00" 1106 | LDSName = "Corner Longitude Point 2 (Full)" 1107 | ESDName = "SAR Longitude 1" 1108 | UDSName = "Corner Longitude Point 2 (Decimal Degrees)" 1109 | _domain = (-(2**31 - 1), 2**31 - 1) 1110 | _range = (-180, 180) 1111 | units = 'degrees' 1112 | 1113 | 1114 | @UASLocalMetadataSet.add_parser 1115 | class CornerLatitudePoint3Full(MappedElementParser): 1116 | key = b'\x56' 1117 | TAG = 86 1118 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 09 01 00" 1119 | LDSName = "Corner Latitude Point 3 (Full)" 1120 | ESDName = "SAR Latitude 2" 1121 | UDSName = "Corner Latitude Point 3 (Decimal Degrees)" 1122 | _domain = (-(2**31 - 1), 2**31 - 1) 1123 | _range = (-90, 90) 1124 | units = 'degrees' 1125 | 1126 | 1127 | @UASLocalMetadataSet.add_parser 1128 | class CornerLongitudePoint3Full(MappedElementParser): 1129 | key = b'\x57' 1130 | TAG = 87 1131 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 0D 01 00" 1132 | LDSName = "Corner Longitude Point 3 (Full)" 1133 | ESDName = "SAR Longitude 2" 1134 | UDSName = "Corner Longitude Point 3 (Decimal Degrees)" 1135 | _domain = (-(2**31 - 1), 2**31 - 1) 1136 | _range = (-180, 180) 1137 | units = 'degrees' 1138 | 1139 | 1140 | @UASLocalMetadataSet.add_parser 1141 | class CornerLatitudePoint4Full(MappedElementParser): 1142 | key = b'\x58' 1143 | TAG = 88 1144 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 0A 01 00" 1145 | LDSName = "Corner Latitude Point 4 (Full)" 1146 | ESDName = "SAR Latitude 3" 1147 | UDSName = "Corner Latitude Point 4 (Decimal Degrees)" 1148 | _domain = (-(2**31 - 1), 2**31 - 1) 1149 | _range = (-90, 90) 1150 | units = 'degrees' 1151 | 1152 | 1153 | @UASLocalMetadataSet.add_parser 1154 | class CornerLongitudePoint4Full(MappedElementParser): 1155 | key = b'\x59' 1156 | TAG = 89 1157 | UDSKey = "06 0E 2B 34 01 01 01 03 07 01 02 01 03 0E 01 00" 1158 | LDSName = "Corner Longitude Point 4 (Full)" 1159 | ESDName = "SAR Longitude 3" 1160 | UDSName = "Corner Longitude Point 4 (Decimal Degrees)" 1161 | _domain = (-(2**31 - 1), 2**31 - 1) 1162 | _range = (-180, 180) 1163 | units = 'degrees' 1164 | 1165 | 1166 | @UASLocalMetadataSet.add_parser 1167 | class PlatformPitchAngleFull(MappedElementParser): 1168 | key = b'\x5A' 1169 | TAG = 90 1170 | UDSKey = "06 0E 2B 34 01 01 01 07 07 01 10 01 05 00 00 00" 1171 | LDSName = "Platform Pitch Angle (Full)" 1172 | ESDName = "UAV Pitch (INS)" 1173 | UDSName = "Platform Pitch Angle" 1174 | _domain = (-(2**31-1), 2**31-1) 1175 | _range = (-90, 90) 1176 | units = 'degrees' 1177 | 1178 | 1179 | @UASLocalMetadataSet.add_parser 1180 | class PlatformRollAngleFull(MappedElementParser): 1181 | key = b'\x5B' 1182 | TAG = 91 1183 | UDSKey = "06 0E 2B 34 01 01 01 07 07 01 10 01 04 00 00 00" 1184 | LDSName = "Platform Roll Angle (Full)" 1185 | ESDName = "UAV Roll (INS)" 1186 | UDSName = "Platform Roll Angle" 1187 | _domain = (-(2**31-1), 2**31-1) 1188 | _range = (-90, 90) 1189 | units = 'degrees' 1190 | 1191 | 1192 | @UASLocalMetadataSet.add_parser 1193 | class PlatformAngleOfAttackFull(MappedElementParser): 1194 | key = b'\x5C' 1195 | TAG = 92 1196 | UDSKey = "-" 1197 | LDSName = "Platform Angle of Attack (Full)" 1198 | ESDName = "" 1199 | UDSName = "" 1200 | _domain = (-(2**31-1), 2**31-1) 1201 | _range = (-90, 90) 1202 | units = 'degrees' 1203 | 1204 | 1205 | @UASLocalMetadataSet.add_parser 1206 | class PlatformSideslipAngleFull(MappedElementParser): 1207 | key = b'\x5D' 1208 | TAG = 93 1209 | UDSKey = "-" 1210 | LDSName = "Platform Sideslip Angle (Full)" 1211 | ESDName = "" 1212 | UDSName = "" 1213 | _domain = (-(2**31-1), 2**31-1) 1214 | _range = (-90, 90) 1215 | units = 'degrees' 1216 | 1217 | 1218 | #@UASLocalMetadataSet.add_parser 1219 | # class MIISCoreIdentifier(StringElementParser): 1220 | # key = b'\x5E' 1221 | # TAG = 94 1222 | # UDSKey = "06 0E 2B 34 01 01 01 01 0E 01 04 05 03 00 00 00" 1223 | # LDSName = "MIIS Core Identifier" 1224 | # ESDName = "" 1225 | # UDSName = "Motion Imagery Identification System Core" 1226 | 1227 | 1228 | #@UASLocalMetadataSet.add_parser 1229 | # class SARMotionImageryLocalSet(StringElementParser): 1230 | # key = b'\x5F' 1231 | # TAG = 95 1232 | # UDSKey = "06 0E 2B 34 02 0B 01 01 0E 01 03 03 0D 00 00 00" 1233 | # LDSName = "SAR Motion Imagery Local Set" 1234 | # ESDName = "" 1235 | # UDSName = "SAR Motion Imagery Local Set" 1236 | 1237 | 1238 | @UASLocalMetadataSet.add_parser 1239 | class TargetWidthExtended(MappedElementParser): 1240 | key = b'\x60' 1241 | TAG = 96 1242 | UDSKey = "06 0E 2B 34 01 01 01 01 07 01 09 02 01 00 00 00" 1243 | LDSName = "Target Width Extended" 1244 | ESDName = "Target Width" 1245 | UDSName = "Target Width" 1246 | _domain = (0, 2**8-1) 1247 | _range = (0, 2**8-1) 1248 | units = 'meters' 1249 | 1250 | 1251 | @UASLocalMetadataSet.add_parser 1252 | class DensityAltitudeExtended(MappedElementParser): 1253 | key = b'\x67' 1254 | TAG = 103 1255 | UDSKey = "06 0E 2B 34 01 01 01 01 0E 01 01 01 10 00 00 00" 1256 | LDSName = "Density Altitude Extended" 1257 | ESDName = "Density Altitude" 1258 | UDSName = "" 1259 | _domain = (0, 2**16-1) 1260 | _range = (-900, 40000) 1261 | units = 'meters' 1262 | 1263 | @UASLocalMetadataSet.add_parser 1264 | class SensorEllipsoidHeightExtended(MappedElementParser): 1265 | key = b'\x68' 1266 | TAG = 104 1267 | UDSKey = "06 0E 2B 34 01 01 01 01 0E 01 02 01 82 47 00 00" 1268 | LDSName = "Sensor Ellipsoid Height Extended" 1269 | ESDName = "" 1270 | UDSName = "" 1271 | _domain = (0, 2**16-1) 1272 | _range = (-900, 40000) 1273 | units = 'meters' 1274 | 1275 | 1276 | @UASLocalMetadataSet.add_parser 1277 | class AlternatePlatformEllipsoidHeightExtended(MappedElementParser): 1278 | key = b'\x69' 1279 | TAG = 105 1280 | UDSKey = "06 0E 2B 34 01 01 01 01 0E 01 02 01 82 48 00 00" 1281 | LDSName = " Alternate Platform Ellipsoid Height Extended" 1282 | ESDName = "" 1283 | UDSName = "" 1284 | _domain = (0, 2**16-1) 1285 | _range = (-900, 40000) 1286 | units = 'meters' 1287 | --------------------------------------------------------------------------------