Skip to content

Commit af61f14

Browse files
authored
Merge pull request #79 from ngjunsiang/master
Parse TransmitterPdu modulation parameters and variable transmitter parameters
2 parents 876748f + 29e084a commit af61f14

File tree

4 files changed

+720
-560
lines changed

4 files changed

+720
-560
lines changed

opendis/dis7.py

Lines changed: 53 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@
1010
ModulationParametersRecord,
1111
UnknownRadio,
1212
UnknownAntennaPattern,
13+
EulerAngles,
14+
BeamAntennaPattern,
15+
GenericRadio,
16+
SimpleIntercomRadio,
17+
BasicHaveQuickMP,
18+
CCTTSincgarsMP,
19+
VariableTransmitterParametersRecord,
20+
HighFidelityHAVEQUICKRadio,
21+
UnknownVariableTransmitterParameters,
1322
)
1423
from .stream import DataInputStream, DataOutputStream
1524
from .types import (
@@ -678,34 +687,6 @@ def parse(self, inputStream):
678687
self.stationNumber = inputStream.read_unsigned_short()
679688

680689

681-
class EulerAngles:
682-
"""Section 6.2.33
683-
684-
Three floating point values representing an orientation, psi, theta,
685-
and phi, aka the euler angles, in radians.
686-
"""
687-
688-
def __init__(self,
689-
psi: float32 = 0.0,
690-
theta: float32 = 0.0,
691-
phi: float32 = 0.0): # in radians
692-
self.psi = psi
693-
self.theta = theta
694-
self.phi = phi
695-
696-
def serialize(self, outputStream):
697-
"""serialize the class"""
698-
outputStream.write_float(self.psi)
699-
outputStream.write_float(self.theta)
700-
outputStream.write_float(self.phi)
701-
702-
def parse(self, inputStream):
703-
"""Parse a message. This may recursively call embedded objects."""
704-
self.psi = inputStream.read_float()
705-
self.theta = inputStream.read_float()
706-
self.phi = inputStream.read_float()
707-
708-
709690
class DirectedEnergyPrecisionAimpoint:
710691
"""Section 6.2.20.3
711692
@@ -843,63 +824,6 @@ def parse(self, inputStream):
843824
self.padding = inputStream.read_unsigned_byte()
844825

845826

846-
class BeamAntennaPattern:
847-
"""Section 6.2.9.2
848-
849-
Used when the antenna pattern type field has a value of 1. Specifies the
850-
direction, pattern, and polarization of radiation from an antenna.
851-
"""
852-
853-
def __init__(self,
854-
beamDirection: "EulerAngles | None" = None,
855-
azimuthBeamwidth: float32 = 0.0, # in radians
856-
elevationBeamwidth: float32 = 0.0, # in radians
857-
referenceSystem: enum8 = 0, # [UID 168]
858-
ez: float32 = 0.0,
859-
ex: float32 = 0.0,
860-
phase: float32 = 0.0): # in radians
861-
self.beamDirection = EulerAngles()
862-
"""The rotation that transforms the reference coordinate sytem into the beam coordinate system. Either world coordinates or entity coordinates may be used as the reference coordinate system, as specified by the reference system field of the antenna pattern record."""
863-
self.azimuthBeamwidth = azimuthBeamwidth
864-
self.elevationBeamwidth = elevationBeamwidth
865-
self.referenceSystem = referenceSystem
866-
self.padding1: uint8 = 0
867-
self.padding2: uint16 = 0
868-
self.ez = ez
869-
"""This field shall specify the magnitude of the Z-component (in beam coordinates) of the Electrical field at some arbitrary single point in the main beam and in the far field of the antenna."""
870-
self.ex = ex
871-
"""This field shall specify the magnitude of the X-component (in beam coordinates) of the Electrical field at some arbitrary single point in the main beam and in the far field of the antenna."""
872-
self.phase = phase
873-
"""This field shall specify the phase angle between EZ and EX in radians. If fully omni-directional antenna is modeled using beam pattern type one, the omni-directional antenna shall be represented by beam direction Euler angles psi, theta, and phi of zero, an azimuth beamwidth of 2PI, and an elevation beamwidth of PI"""
874-
self.padding3: uint32 = 0
875-
876-
def serialize(self, outputStream):
877-
"""serialize the class"""
878-
self.beamDirection.serialize(outputStream)
879-
outputStream.write_float(self.azimuthBeamwidth)
880-
outputStream.write_float(self.elevationBeamwidth)
881-
outputStream.write_unsigned_byte(self.referenceSystem)
882-
outputStream.write_unsigned_byte(self.padding1)
883-
outputStream.write_unsigned_short(self.padding2)
884-
outputStream.write_float(self.ez)
885-
outputStream.write_float(self.ex)
886-
outputStream.write_float(self.phase)
887-
outputStream.write_unsigned_int(self.padding3)
888-
889-
def parse(self, inputStream):
890-
"""Parse a message. This may recursively call embedded objects."""
891-
self.beamDirection.parse(inputStream)
892-
self.azimuthBeamwidth = inputStream.read_float()
893-
self.elevationBeamwidth = inputStream.read_float()
894-
self.referenceSystem = inputStream.read_unsigned_byte()
895-
self.padding1 = inputStream.read_unsigned_byte()
896-
self.padding2 = inputStream.read_unsigned_short()
897-
self.ez = inputStream.read_float()
898-
self.ex = inputStream.read_float()
899-
self.phase = inputStream.read_float()
900-
self.padding3 = inputStream.read_unsigned_int()
901-
902-
903827
class AttachedParts:
904828
"""Section 6.2.93.3
905829
@@ -938,36 +862,6 @@ def parse(self, inputStream):
938862
self.parameterValue = inputStream.read_long()
939863

940864

941-
class VariableTransmitterParameters:
942-
"""Section 6.2.94
943-
944-
Relates to radios. NOT COMPLETE.
945-
"""
946-
947-
def __init__(self, recordType: enum32 = 0, data: bytes = b""):
948-
self.recordType = recordType # [UID 66] Variable Parameter Record Type
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()
957-
958-
def serialize(self, outputStream: DataOutputStream) -> None:
959-
"""serialize the class"""
960-
outputStream.write_uint32(self.recordType)
961-
outputStream.write_uint16(self.recordLength)
962-
outputStream.write_bytes(self.data)
963-
964-
def parse(self, inputStream: DataInputStream) -> None:
965-
"""Parse a message. This may recursively call embedded objects."""
966-
self.recordType = inputStream.read_uint32()
967-
recordLength = inputStream.read_uint16()
968-
self.data = inputStream.read_bytes(recordLength)
969-
970-
971865
class Attribute:
972866
"""Section 6.2.10.
973867
@@ -5411,11 +5305,11 @@ def parse(self, inputStream):
54115305

54125306

54135307
class TransmitterPdu(RadioCommunicationsFamilyPdu):
5414-
"""Section 7.7.2
5308+
"""7.7.2 Transmitter PDU
54155309
54165310
Detailed information about a radio transmitter. This PDU requires manually
54175311
written code to complete, since the modulation parameters are of variable
5418-
length. UNFINISHED
5312+
length.
54195313
"""
54205314
pduType: enum8 = 25 # [UID 4]
54215315

@@ -5436,7 +5330,7 @@ def __init__(self,
54365330
cryptoKeyId: struct16 = 0, # See Table 175
54375331
modulationParameters: ModulationParametersRecord | None = None,
54385332
antennaPattern: AntennaPatternRecord | None = None,
5439-
variableTransmitterParameters: Sequence[VariableTransmitterParameters] | None = None):
5333+
variableTransmitterParameters: Sequence[VariableTransmitterParametersRecord] | None = None):
54405334
super(TransmitterPdu, self).__init__()
54415335
self.radioReferenceID = radioReferenceID or EntityID()
54425336
"""ID of the entity that is the source of the communication"""
@@ -5459,7 +5353,11 @@ def __init__(self,
54595353
self.padding3 = 0
54605354
self.modulationParameters = modulationParameters
54615355
self.antennaPattern = antennaPattern
5462-
self.variableTransmitterParameters = variableTransmitterParameters or []
5356+
self.variableTransmitterParameters = (
5357+
list(variableTransmitterParameters)
5358+
if variableTransmitterParameters
5359+
else []
5360+
)
54635361

54645362
@property
54655363
def antennaPatternLength(self) -> uint16:
@@ -5542,23 +5440,50 @@ def parse(self, inputStream: DataInputStream) -> None:
55425440

55435441
## Modulation Parameters
55445442
if modulationParametersLength > 0:
5545-
radio = UnknownRadio()
5546-
radio.parse(inputStream, bytelength=modulationParametersLength)
5443+
if self.modulationType.radioSystem == 1: # Generic | Simple Intercom
5444+
if self.modulationType.majorModulation == 0:
5445+
radio = SimpleIntercomRadio()
5446+
else:
5447+
radio = GenericRadio()
5448+
radio.parse(inputStream)
5449+
elif self.modulationType.radioSystem in (2, 3, 4): # HAVE QUICK I | II | HAVE QUICK IIA
5450+
radio = BasicHaveQuickMP()
5451+
radio.parse(inputStream)
5452+
elif self.modulationType.radioSystem == 6: # CCTT SINCGARS
5453+
radio = CCTTSincgarsMP()
5454+
radio.parse(inputStream)
5455+
else: # Other | Unknown
5456+
radio = UnknownRadio()
5457+
radio.parse(inputStream, bytelength=modulationParametersLength)
55475458
self.modulationParameters = radio
55485459
else:
55495460
self.modulationParameters = None
55505461

55515462
## Antenna Pattern
55525463
if antennaPatternLength > 0:
5553-
self.antennaPattern = UnknownAntennaPattern()
5554-
self.antennaPattern.parse(
5555-
inputStream,
5556-
bytelength=antennaPatternLength
5557-
)
5464+
if self.antennaPatternType == 1:
5465+
self.antennaPattern = BeamAntennaPattern()
5466+
self.antennaPattern.parse(inputStream)
5467+
else:
5468+
self.antennaPattern = UnknownAntennaPattern()
5469+
self.antennaPattern.parse(
5470+
inputStream,
5471+
bytelength=antennaPatternLength
5472+
)
55585473
else:
55595474
self.antennaPattern = None
5560-
5561-
5475+
5476+
## TODO: Variable Transmitter Parameters
5477+
for _ in range(0, variableTransmitterParameterCount):
5478+
recordType = inputStream.read_uint32()
5479+
if recordType == 3000: # High Fidelity HAVE QUICK/SATURN Radio
5480+
vtp = HighFidelityHAVEQUICKRadio()
5481+
vtp.parse(inputStream)
5482+
else: # Unknown VTP record type
5483+
vtp = UnknownVariableTransmitterParameters()
5484+
vtp.recordType = recordType
5485+
vtp.parse(inputStream)
5486+
self.variableTransmitterParameters.append(vtp)
55625487

55635488

55645489
class ElectromagneticEmissionsPdu(DistributedEmissionsFamilyPdu):

0 commit comments

Comments
 (0)