Skip to content

Commit 876748f

Browse files
authored
Merge pull request #78 from ngjunsiang/feat-record-class
feat: add placeholder implementation for ModulationParameters, AntennaPattern, and VariableTransmitterParameters
2 parents ac84222 + b13d41e commit 876748f

File tree

2 files changed

+344
-140
lines changed

2 files changed

+344
-140
lines changed

opendis/dis7.py

Lines changed: 112 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
#This code is licensed under the BSD software license
33
#
44

5+
from typing import Sequence
6+
7+
from .record import (
8+
AntennaPatternRecord,
9+
ModulationType,
10+
ModulationParametersRecord,
11+
UnknownRadio,
12+
UnknownAntennaPattern,
13+
)
14+
from .stream import DataInputStream, DataOutputStream
515
from .types import (
616
enum8,
717
enum16,
@@ -17,7 +27,6 @@
1727
struct16,
1828
struct32,
1929
)
20-
from .record import SpreadSpectrum
2130

2231

2332
class DataQueryDatumSpecification:
@@ -669,22 +678,6 @@ def parse(self, inputStream):
669678
self.stationNumber = inputStream.read_unsigned_short()
670679

671680

672-
class ModulationParameters:
673-
"""Section 6.2.58
674-
675-
Modulation parameters associated with a specific radio system. INCOMPLETE.
676-
"""
677-
678-
def __init__(self):
679-
pass
680-
681-
def serialize(self, outputStream):
682-
"""serialize the class"""
683-
684-
def parse(self, inputStream):
685-
"""Parse a message. This may recursively call embedded objects."""
686-
687-
688681
class EulerAngles:
689682
"""Section 6.2.33
690683
@@ -951,21 +944,28 @@ class VariableTransmitterParameters:
951944
Relates to radios. NOT COMPLETE.
952945
"""
953946

954-
def __init__(self, recordType: enum32 = 0, recordLength: uint16 = 4):
947+
def __init__(self, recordType: enum32 = 0, data: bytes = b""):
955948
self.recordType = recordType # [UID 66] Variable Parameter Record Type
956-
"""Type of VTP. Enumeration from EBV"""
957-
self.recordLength = recordLength
958-
"""Length, in bytes"""
949+
self.data = data
950+
951+
def marshalledSize(self) -> int:
952+
return 6 + len(self.data)
953+
954+
@property
955+
def recordLength(self) -> uint16:
956+
return self.marshalledSize()
959957

960-
def serialize(self, outputStream):
958+
def serialize(self, outputStream: DataOutputStream) -> None:
961959
"""serialize the class"""
962-
outputStream.write_unsigned_int(self.recordType)
963-
outputStream.write_unsigned_int(self.recordLength)
960+
outputStream.write_uint32(self.recordType)
961+
outputStream.write_uint16(self.recordLength)
962+
outputStream.write_bytes(self.data)
964963

965-
def parse(self, inputStream):
964+
def parse(self, inputStream: DataInputStream) -> None:
966965
"""Parse a message. This may recursively call embedded objects."""
967-
self.recordType = inputStream.read_unsigned_int()
968-
self.recordLength = inputStream.read_unsigned_int()
966+
self.recordType = inputStream.read_uint32()
967+
recordLength = inputStream.read_uint16()
968+
self.data = inputStream.read_bytes(recordLength)
969969

970970

971971
class Attribute:
@@ -2017,41 +2017,6 @@ def parse(self, inputStream):
20172017
self.communicationsNodeID.parse(inputStream)
20182018

20192019

2020-
class ModulationType:
2021-
"""Section 6.2.59
2022-
2023-
Information about the type of modulation used for radio transmission.
2024-
"""
2025-
2026-
def __init__(self,
2027-
spreadSpectrum: SpreadSpectrum | None = None, # See RPR Enumerations
2028-
majorModulation: enum16 = 0, # [UID 155]
2029-
detail: enum16 = 0, # [UID 156-162]
2030-
radioSystem: enum16 = 0): # [UID 163]
2031-
self.spreadSpectrum = spreadSpectrum or SpreadSpectrum()
2032-
"""This field shall indicate the spread spectrum technique or combination of spread spectrum techniques in use. Bit field. 0=freq hopping, 1=psuedo noise, time hopping=2, reamining bits unused"""
2033-
self.majorModulation = majorModulation
2034-
"""the major classification of the modulation type."""
2035-
self.detail = detail
2036-
"""provide certain detailed information depending upon the major modulation type"""
2037-
self.radioSystem = radioSystem
2038-
"""the radio system associated with this Transmitter PDU and shall be used as the basis to interpret other fields whose values depend on a specific radio system."""
2039-
2040-
def serialize(self, outputStream):
2041-
"""serialize the class"""
2042-
outputStream.write_unsigned_short(self.spreadSpectrum)
2043-
outputStream.write_unsigned_short(self.majorModulation)
2044-
outputStream.write_unsigned_short(self.detail)
2045-
outputStream.write_unsigned_short(self.radioSystem)
2046-
2047-
def parse(self, inputStream):
2048-
"""Parse a message. This may recursively call embedded objects."""
2049-
self.spreadSpectrum = inputStream.read_unsigned_short()
2050-
self.majorModulation = inputStream.read_unsigned_short()
2051-
self.detail = inputStream.read_unsigned_short()
2052-
self.radioSystem = inputStream.read_unsigned_short()
2053-
2054-
20552020
class LinearSegmentParameter:
20562021
"""Section 6.2.52
20572022
@@ -5460,20 +5425,18 @@ def __init__(self,
54605425
radioEntityType: "EntityType | None" = None,
54615426
transmitState: enum8 = 0, # [UID 164]
54625427
inputSource: enum8 = 0, # [UID 165]
5463-
variableTransmitterParameterCount: uint16 = 0,
54645428
antennaLocation: "Vector3Double | None" = None,
54655429
relativeAntennaLocation: "Vector3Float | None" = None,
54665430
antennaPatternType: enum16 = 0, # [UID 167]
5467-
antennaPatternCount: uint16 = 0, # in bytes
54685431
frequency: uint64 = 0, # in Hz
54695432
transmitFrequencyBandwidth: float32 = 0.0, # in Hz
54705433
power: float32 = 0.0, # in decibel-milliwatts
54715434
modulationType: "ModulationType | None" = None,
54725435
cryptoSystem: enum16 = 0, # [UID 166]
54735436
cryptoKeyId: struct16 = 0, # See Table 175
5474-
modulationParameterCount: uint8 = 0, # in bytes
5475-
modulationParametersList=None,
5476-
antennaPatternList=None):
5437+
modulationParameters: ModulationParametersRecord | None = None,
5438+
antennaPattern: AntennaPatternRecord | None = None,
5439+
variableTransmitterParameters: Sequence[VariableTransmitterParameters] | None = None):
54775440
super(TransmitterPdu, self).__init__()
54785441
self.radioReferenceID = radioReferenceID or EntityID()
54795442
"""ID of the entity that is the source of the communication"""
@@ -5486,107 +5449,116 @@ def __init__(self,
54865449
self.relativeAntennaLocation = relativeAntennaLocation or Vector3Float(
54875450
)
54885451
self.antennaPatternType = antennaPatternType
5489-
self.antennaPatternCount = antennaPatternCount
54905452
self.frequency = frequency
54915453
self.transmitFrequencyBandwidth = transmitFrequencyBandwidth
54925454
self.power = power
54935455
self.modulationType = modulationType or ModulationType()
54945456
self.cryptoSystem = cryptoSystem
54955457
self.cryptoKeyId = cryptoKeyId
5496-
# FIXME: Refactor modulation parameters into its own record class
5497-
self.modulationParameterCount = modulationParameterCount
54985458
self.padding2 = 0
54995459
self.padding3 = 0
5500-
self.modulationParametersList = modulationParametersList or []
5501-
self.antennaPatternList = antennaPatternList or []
5502-
# TODO: zero or more Variable Transmitter Parameters records (see 6.2.95)
5460+
self.modulationParameters = modulationParameters
5461+
self.antennaPattern = antennaPattern
5462+
self.variableTransmitterParameters = variableTransmitterParameters or []
5463+
5464+
@property
5465+
def antennaPatternLength(self) -> uint16:
5466+
if self.antennaPattern:
5467+
return self.antennaPattern.marshalledSize()
5468+
else:
5469+
return 0
5470+
5471+
@property
5472+
def modulationParametersLength(self) -> uint8:
5473+
if self.modulationParameters:
5474+
return self.modulationParameters.marshalledSize()
5475+
else:
5476+
return 0
55035477

55045478
@property
55055479
def variableTransmitterParameterCount(self) -> uint16:
5506-
"""How many variable transmitter parameters are in the variable length list.
5507-
In earlier versions of DIS these were known as articulation parameters.
5508-
"""
5509-
return len(self.modulationParametersList)
5480+
return len(self.variableTransmitterParameters)
55105481

5511-
def serialize(self, outputStream):
5512-
"""serialize the class"""
5482+
def serialize(self, outputStream: DataOutputStream) -> None:
55135483
super(TransmitterPdu, self).serialize(outputStream)
55145484
self.radioReferenceID.serialize(outputStream)
5515-
outputStream.write_unsigned_short(self.radioNumber)
5485+
outputStream.write_uint16(self.radioNumber)
55165486
self.radioEntityType.serialize(outputStream)
5517-
outputStream.write_unsigned_byte(self.transmitState)
5518-
outputStream.write_unsigned_byte(self.inputSource)
5519-
outputStream.write_unsigned_short(
5520-
self.variableTransmitterParameterCount)
5487+
outputStream.write_uint8(self.transmitState)
5488+
outputStream.write_uint8(self.inputSource)
5489+
outputStream.write_uint16(self.variableTransmitterParameterCount)
55215490
self.antennaLocation.serialize(outputStream)
55225491
self.relativeAntennaLocation.serialize(outputStream)
5523-
outputStream.write_unsigned_short(self.antennaPatternType)
5524-
outputStream.write_unsigned_short(len(self.antennaPatternList))
5525-
outputStream.write_long(self.frequency)
5526-
outputStream.write_float(self.transmitFrequencyBandwidth)
5527-
outputStream.write_float(self.power)
5492+
outputStream.write_uint16(self.antennaPatternType)
5493+
outputStream.write_uint16(self.antennaPatternLength)
5494+
outputStream.write_uint64(self.frequency)
5495+
outputStream.write_float32(self.transmitFrequencyBandwidth)
5496+
outputStream.write_float32(self.power)
55285497
self.modulationType.serialize(outputStream)
5529-
outputStream.write_unsigned_short(self.cryptoSystem)
5530-
outputStream.write_unsigned_short(self.cryptoKeyId)
5531-
outputStream.write_unsigned_byte(len(self.modulationParametersList))
5532-
outputStream.write_unsigned_short(self.padding2)
5533-
outputStream.write_unsigned_byte(self.padding3)
5534-
for anObj in self.modulationParametersList:
5535-
anObj.serialize(outputStream)
5498+
outputStream.write_uint16(self.cryptoSystem)
5499+
outputStream.write_uint16(self.cryptoKeyId)
5500+
outputStream.write_uint8(self.modulationParametersLength)
5501+
outputStream.write_uint16(self.padding2)
5502+
outputStream.write_uint8(self.padding3)
55365503

5537-
for anObj in self.antennaPatternList:
5538-
anObj.serialize(outputStream)
5504+
# Serialize parameter records
55395505

5540-
def parse(self, inputStream):
5506+
## Modulation Parameters
5507+
if self.modulationParameters:
5508+
self.modulationParameters.serialize(outputStream)
5509+
5510+
## Antenna Pattern
5511+
if self.antennaPattern:
5512+
self.antennaPattern.serialize(outputStream)
5513+
5514+
## Variable Transmitter Parameters
5515+
for vtp in self.variableTransmitterParameters:
5516+
vtp.serialize(outputStream)
5517+
5518+
def parse(self, inputStream: DataInputStream) -> None:
55415519
"""Parse a message. This may recursively call embedded objects."""
55425520
super(TransmitterPdu, self).parse(inputStream)
55435521
self.radioReferenceID.parse(inputStream)
5544-
self.radioNumber = inputStream.read_unsigned_short()
5522+
self.radioNumber = inputStream.read_uint16()
55455523
self.radioEntityType.parse(inputStream)
5546-
self.transmitState = inputStream.read_unsigned_byte()
5547-
self.inputSource = inputStream.read_unsigned_byte()
5548-
variableTransmitterParameterCount = inputStream.read_unsigned_short(
5549-
)
5524+
self.transmitState = inputStream.read_uint8()
5525+
self.inputSource = inputStream.read_uint8()
5526+
variableTransmitterParameterCount = inputStream.read_uint16()
55505527
self.antennaLocation.parse(inputStream)
55515528
self.relativeAntennaLocation.parse(inputStream)
5552-
self.antennaPatternType = inputStream.read_unsigned_short()
5553-
self.antennaPatternCount = inputStream.read_unsigned_short()
5554-
self.frequency = inputStream.read_long()
5555-
self.transmitFrequencyBandwidth = inputStream.read_float()
5556-
self.power = inputStream.read_float()
5529+
self.antennaPatternType = inputStream.read_uint16()
5530+
antennaPatternLength = inputStream.read_uint16()
5531+
self.frequency = inputStream.read_uint64()
5532+
self.transmitFrequencyBandwidth = inputStream.read_float32()
5533+
self.power = inputStream.read_float32()
55575534
self.modulationType.parse(inputStream)
5558-
self.cryptoSystem = inputStream.read_unsigned_short()
5559-
self.cryptoKeyId = inputStream.read_unsigned_short()
5560-
self.modulationParameterCount = inputStream.read_unsigned_byte()
5561-
self.padding2 = inputStream.read_unsigned_short()
5562-
self.padding3 = inputStream.read_unsigned_byte()
5563-
"""Vendor product MACE from BattleSpace Inc, only uses 1 byte per modulation param"""
5564-
"""SISO Spec dictates it should be 2 bytes"""
5565-
"""Instead of dumping the packet we can make an assumption that some vendors use 1 byte per param"""
5566-
"""Although we will still send out 2 bytes per param as per spec"""
5567-
endsize = self.antennaPatternCount * 39
5568-
mod_bytes = 2
5569-
5570-
if (self.modulationParameterCount > 0):
5571-
curr = inputStream.stream.tell()
5572-
remaining = inputStream.stream.read(None)
5573-
mod_bytes = (len(remaining) -
5574-
endsize) / self.modulationParameterCount
5575-
inputStream.stream.seek(curr, 0)
5576-
5577-
if (mod_bytes > 2):
5578-
print("Malformed Packet")
5535+
self.cryptoSystem = inputStream.read_uint16()
5536+
self.cryptoKeyId = inputStream.read_uint16()
5537+
modulationParametersLength = inputStream.read_uint8()
5538+
self.padding2 = inputStream.read_uint16()
5539+
self.padding3 = inputStream.read_uint8()
5540+
5541+
# Parse parameter records
5542+
5543+
## Modulation Parameters
5544+
if modulationParametersLength > 0:
5545+
radio = UnknownRadio()
5546+
radio.parse(inputStream, bytelength=modulationParametersLength)
5547+
self.modulationParameters = radio
55795548
else:
5580-
for idx in range(0, self.modulationParameterCount):
5581-
if mod_bytes == 2:
5582-
element = inputStream.read_unsigned_short()
5583-
else:
5584-
element = inputStream.read_unsigned_byte()
5585-
self.modulationParametersList.append(element)
5586-
for idx in range(0, self.antennaPatternCount):
5587-
element = BeamAntennaPattern()
5588-
element.parse(inputStream)
5589-
self.antennaPatternList.append(element)
5549+
self.modulationParameters = None
5550+
5551+
## Antenna Pattern
5552+
if antennaPatternLength > 0:
5553+
self.antennaPattern = UnknownAntennaPattern()
5554+
self.antennaPattern.parse(
5555+
inputStream,
5556+
bytelength=antennaPatternLength
5557+
)
5558+
else:
5559+
self.antennaPattern = None
5560+
5561+
55905562

55915563

55925564
class ElectromagneticEmissionsPdu(DistributedEmissionsFamilyPdu):

0 commit comments

Comments
 (0)