Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions .basedpyright/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -2023,22 +2023,6 @@
"lineCount": 1
}
},
{
"code": "reportArgumentType",
"range": {
"startColumn": 12,
"endColumn": 44,
"lineCount": 1
}
},
{
"code": "reportArgumentType",
"range": {
"startColumn": 12,
"endColumn": 44,
"lineCount": 1
}
},
{
"code": "reportOptionalMemberAccess",
"range": {
Expand Down
146 changes: 132 additions & 14 deletions monitoring/monitorlib/clients/flight_planning/flight_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from uas_standards.interuss.automated_testing.flight_planning.v1 import api as fp_api
from uas_standards.interuss.automated_testing.scd.v1 import api as scd_api

from monitoring.monitorlib.clients.flight_planning.telemetry import FlightTelemetry
from monitoring.monitorlib.geo import Altitude, LatLngPoint
from monitoring.monitorlib.geotemporal import Volume4D, Volume4DCollection

# ===== ASTM F3548-21 =====
Expand Down Expand Up @@ -250,11 +252,107 @@ def f3548v21_op_intent_state(self) -> f3548v21.OperationalIntentState:
return state


class UAType(str, Enum):
"""The UA Type can help infer performance, speed, and duration of flights, for example, a
"fixed wing" can generally fly in a forward direction only (as compared to a multi-rotor).

`HybridLift` is a fixed wing aircraft that can take off vertically. `Helicopter` includes multirotor.

`VTOL` is equivalent to HybridLift.
"""

NotDeclared = "NotDeclared"
Aeroplane = "Aeroplane"
Helicopter = "Helicopter"
Gyroplane = "Gyroplane"
VTOL = "VTOL"
HybridLift = "HybridLift"
Ornithopter = "Ornithopter"
Glider = "Glider"
Kite = "Kite"
FreeBalloon = "FreeBalloon"
CaptiveBalloon = "CaptiveBalloon"
Airship = "Airship"
FreeFallOrParachute = "FreeFallOrParachute"
Rocket = "Rocket"
TetheredPoweredAircraft = "TetheredPoweredAircraft"
GroundObstacle = "GroundObstacle"
Other = "Other"


class UASRegistrationNumber(ImplicitDict):
"""Number provided by CAA or authorized representative for registering and/or identifying UAS."""

authority: str | None = ""
"""Authority providing this registration number. If authority represents a country, the ICAO nationality
mark is recommended.
"""

identifier: str
"""Authority-assigned number or identifier."""


class UAClassificationEUCategory(str, Enum):
EUCategoryUndefined = "EUCategoryUndefined"
Open = "Open"
Specific = "Specific"
Certified = "Certified"


class UAClassificationEUClass(str, Enum):
EUClassUndefined = "EUClassUndefined"
Class0 = "Class0"
Class1 = "Class1"
Class2 = "Class2"
Class3 = "Class3"
Class4 = "Class4"
Class5 = "Class5"
Class6 = "Class6"


UAClassificationEU = dict[UAClassificationEUCategory, UAClassificationEUClass]


class UASInformation(ImplicitDict):
"""Information about a UAS that may be provided in flight planning scenarios."""

aircraft_type: UAType | None
"""Aircraft type of the injected test flight."""

serial_number: str | None = ""
"""This is generally expressed in the CTA-2063-A Serial Number format."""

registration_numbers: list[UASRegistrationNumber] | None = []
"""For each relevant authority with which this UAS is registered, the number/identifier assigned to this UAS."""

eu_classification: UAClassificationEU | None
"""EU classification of aircraft."""


class OperatorInformation(ImplicitDict):
"""Information about the operator that may be provided in flight planning scenarios."""

registration_numbers: list[OperatorRegistrationNumber] | None
"""Registration numbers for the remote pilot or operator."""

location: LatLngPoint | None
"""Location of operator."""

altitude: Altitude | None
"""Altitude of operator."""


class FlightInfo(ImplicitDict):
"""Details of user's intent to create or modify a flight plan."""

basic_information: BasicFlightPlanInformation

uas: UASInformation | None

operator: OperatorInformation | None

telemetry: FlightTelemetry | None

astm_f3548_21: Optional[ASTMF354821OpIntentInformation]

uspace_flight_authorisation: Optional[FlightAuthorisationData]
Expand All @@ -266,44 +364,64 @@ class FlightInfo(ImplicitDict):

@staticmethod
def from_flight_plan(plan: fp_api.FlightPlan) -> FlightInfo:
kwargs = {
"basic_information": BasicFlightPlanInformation.from_flight_planning_api(
result = FlightInfo(
basic_information=BasicFlightPlanInformation.from_flight_planning_api(
plan.basic_information
)
}
)
if "uas" in plan and plan.uas:
result.uas = ImplicitDict.parse(plan.uas, UASInformation)
if "operator" in plan and plan.operator:
result.operator = ImplicitDict.parse(plan.operator, OperatorInformation)
if "telemetry" in plan and plan.telemetry:
result.telemetry = ImplicitDict.parse(plan.telemetry, FlightTelemetry)
if "astm_f3548_21" in plan and plan.astm_f3548_21:
kwargs["astm_f3548_21"] = ImplicitDict.parse(
result.astm_f3548_21 = ImplicitDict.parse(
plan.astm_f3548_21, ASTMF354821OpIntentInformation
)
if "uspace_flight_authorisation" in plan and plan.uspace_flight_authorisation:
kwargs["uspace_flight_authorisation"] = ImplicitDict.parse(
result.uspace_flight_authorisation = ImplicitDict.parse(
plan.uspace_flight_authorisation, FlightAuthorisationData
)
if "rpas_operating_rules_2_6" in plan and plan.rpas_operating_rules_2_6:
kwargs["rpas_operating_rules_2_6"] = ImplicitDict.parse(
result.rpas_operating_rules_2_6 = ImplicitDict.parse(
plan.rpas_operating_rules_2_6, RPAS26FlightDetails
)
if "additional_information" in plan and plan.additional_information:
kwargs["additional_information"] = plan.additional_information
return FlightInfo(**kwargs)
result.additional_information = plan.additional_information
return result

def to_flight_plan(self) -> fp_api.FlightPlan:
kwargs = {"basic_information": self.basic_information.to_flight_planning_api()}
result = fp_api.FlightPlan(
basic_information=self.basic_information.to_flight_planning_api()
)
if "uas" in self and self.uas:
result.uas = ImplicitDict.parse(self.uas, fp_api.UASInformation)
if "operator" in self and self.operator:
result.operator = ImplicitDict.parse(
self.operator, fp_api.OperatorInformation
)
if "telemetry" in self and self.telemetry:
result.telemetry = ImplicitDict.parse(
self.telemetry, fp_api.FlightTelemetry
)
if "astm_f3548_21" in self and self.astm_f3548_21:
kwargs["astm_f3548_21"] = ImplicitDict.parse(
result.astm_f3548_21 = ImplicitDict.parse(
self.astm_f3548_21, fp_api.ASTMF354821OpIntentInformation
)
if "uspace_flight_authorisation" in self and self.uspace_flight_authorisation:
kwargs["uspace_flight_authorisation"] = ImplicitDict.parse(
result.uspace_flight_authorisation = ImplicitDict.parse(
self.uspace_flight_authorisation, fp_api.FlightAuthorisationData
)
if "rpas_operating_rules_2_6" in self and self.rpas_operating_rules_2_6:
kwargs["rpas_operating_rules_2_6"] = ImplicitDict.parse(
result.rpas_operating_rules_2_6 = ImplicitDict.parse(
self.rpas_operating_rules_2_6, fp_api.RPAS26FlightDetails
)
if "additional_information" in self and self.additional_information:
kwargs["additional_information"] = self.additional_information
return fp_api.FlightPlan(**kwargs)
result.additional_information = fp_api.FlightPlanAdditionalInformation()
for k, v in self.additional_information.items():
result.additional_information[k] = v
return result

@staticmethod
def from_scd_inject_flight_request(
Expand Down
148 changes: 148 additions & 0 deletions monitoring/monitorlib/clients/flight_planning/telemetry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
from enum import Enum

from implicitdict import ImplicitDict

from monitoring.monitorlib.geo import Altitude, LatLngPoint
from monitoring.monitorlib.temporal import Time


class HorizontalAccuracy(str, Enum):
"""This is the NACp enumeration from ADS-B, plus 1m for a more complete range for UAs."""

HAUnknown = "HAUnknown"
HA10NMPlus = "HA10NMPlus"
HA10NM = "HA10NM"
HA4NM = "HA4NM"
HA2NM = "HA2NM"
HA1NM = "HA1NM"
HA05NM = "HA05NM"
HA03NM = "HA03NM"
HA01NM = "HA01NM"
HA005NM = "HA005NM"
HA30m = "HA30m"
HA10m = "HA10m"
HA3m = "HA3m"
HA1m = "HA1m"


class VerticalAccuracy(str, Enum):
"""This is the GVA enumeration from ADS-B, plus some finer values for UAs."""

VAUnknown = "VAUnknown"
VA150mPlus = "VA150mPlus"
VA150m = "VA150m"
VA45m = "VA45m"
VA25m = "VA25m"
VA10m = "VA10m"
VA3m = "VA3m"
VA1m = "VA1m"


class SpeedAccuracy(str, Enum):
"""This is the same enumeration scale and values from ADS-B NACv."""

SAUnknown = "SAUnknown"
SA10mpsPlus = "SA10mpsPlus"
SA10mps = "SA10mps"
SA3mps = "SA3mps"
SA1mps = "SA1mps"
SA03mps = "SA03mps"


class OperationalStatus(str, Enum):
"""Indicates operational status of associated aircraft.
* `Undeclared`: The system does not support acquisition of knowledge about the status of the aircraft.
* `Ground`: The aircraft is reporting status but is not airborne.
* `Airborne`: The aircraft is, or should be considered as, being airborne.
* `Emergency`: The aircraft is reporting an emergency.
* `SystemFailure`: Some aspect of the reporting/telemetry system has failed, but the aircraft is not in emergency.
* `Unknown`: The system supports acquisition of knowledge about the status of the aircraft, but the status cannot currently be determined.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RID standard has a value RemoteIDSystemFailure, from the description I assume this falls into this Unknown case. If so, document this here out of clarity?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The immediate reason this enum doesn't have RemoteIDSystemFailure is because the flight_planning API doesn't have RemoteIDSystemFailure. I don't remember a reason why this option would have been intentionally omitted from the API, so I'll add it here to the internal representation and consider adding to the flight_planning API as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When adding an option, I think I may have remembered: these states are supposed to be general to the flight (flight_planning) and the remote ID system specifically having a failure is hard to fit into that paradigm -- presumably remote ID subsystem failure would be reported/specified in another part of the flight description defining subsystem functionality. I've sidestepped this for now by extending this option more broadly to a failure regarding the reporting/telemetry system. This should be applicable to flights in general, and could potentially be used directly for remote ID testing.

"""

Undeclared = "Undeclared"
Ground = "Ground"
Airborne = "Airborne"
Emergency = "Emergency"
SystemFailure = "SystemFailure"
Unknown = "Unknown"


class FunctionalState(str, Enum):
"""Functional state of the user's UAS associated with this flight plan.

- `Nominal`: The user or UAS reports or implies that it is performing nominally, or has not indicated
`OffNominal` or `Contingent`.

- `OffNominal`: The user or UAS reports or implies that it is temporarily not performing nominally, but
may expect to be able to recover to normal operation.

- `Contingent`: The user or UAS reports or implies that it is not performing nominally and may be unable
to recover to normal operation.

- `NotSpecified`: The UAS status is not currently available or known (for instance, if the flight is
planned in the future and the UAS that will be flying has not yet connected to the system).
"""

Nominal = "Nominal"
OffNominal = "OffNominal"
Contingent = "Contingent"
NotSpecified = "NotSpecified"


class AircraftPosition(ImplicitDict):
"""Reported or actual position of an aircraft at a particular time."""

location: LatLngPoint | None

altitudes: list[Altitude] | None
"""The single vertical location of the aircraft, potentially reported relative to multiple datums."""

accuracy_h: HorizontalAccuracy | None
"""Horizontal error that may be be present in this reported position."""

accuracy_v: VerticalAccuracy | None
"""Vertical error that may be present in this reported position."""

pressure_altitude: float | None = 0.0
"""The uncorrected altitude (based on reference standard 29.92 inHg, 1013.25 mb) provides a reference for algorithms that utilize "altitude deltas" between aircraft. This value is provided in meters."""


class AircraftState(ImplicitDict):
"""State of an aircraft for the purposes of simulating the execution of a flight plan."""

id: str
"""Unique identifier for this aircraft state/telemetry. The content of an AircraftState with a given ID must not change over time. Therefore, if a USS has already queued an aircraft state with this ID as telemetry to be delivered, this state may be ignored."""

timestamp: Time
"""Time at which this state is valid. This is equivalent to the time of sensor measurement, so the USS's primary system under test should not be aware of this state until after this time."""

timestamp_accuracy: float | None = 0.0
"""Declaration of timestamp accuracy, which is the one-sided width of the 95%-confidence interval around `timestamp` for the true time of applicability for any of the data fields."""

position: AircraftPosition | None

operational_status: OperationalStatus | None

uas_state: FunctionalState | None
"""State of the user's UAS associated with this flight plan as reported by the operator. If the UAS reports its own state, that report will be reflected here. If the operator reports the UAS state, that report will be reflected in the BasicFlightPlanInformation.
If a system accepts UAS state reports from both the operator and UAS, it is possible the reported state may differ between those two sources.
"""

track: float | None = 0.0
"""Direction of flight expressed as a "True North-based" ground track angle. This value is provided in degrees East of North. A value of 360 indicates that the true value is unavailable."""

speed: float | None = 0.0
"""Ground speed of flight in meters per second."""

speed_accuracy: SpeedAccuracy | None
"""Accuracy of horizontal ground speed."""

vertical_speed: float | None = 0.0
"""Geodetic vertical speed upward. Units of meters per second."""


class FlightTelemetry(ImplicitDict):
"""When this information is present and the USS supports telemetry ingestion for a flight, the USS receiving this information should enqueue these telemetry reports to be delivered to the system, as if they were coming from the user, at (or soon after) the specified times."""

states: list[AircraftState] | None = []
"""The set of telemetry data that should be injected into the system (as if reported by the user or the user's system) at the appropriate times (and not before) for this flight."""
Loading