From 237cb29904f13cfefd3b10d465492c3bbd52412f Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Fri, 28 Jul 2023 14:56:17 +0200 Subject: [PATCH 01/37] [rid] NET0470 Operator ID --- monitoring/mock_uss/riddp/routes_observation.py | 11 +++++++++-- monitoring/monitorlib/fetch/rid.py | 4 ++++ .../rid_automated_testing/observation_api.py | 3 ++- .../astm/netrid/common_dictionary_evaluator.py | 8 +++++++- .../scenarios/astm/netrid/display_data_evaluator.py | 13 ++++++++++++- .../scenarios/astm/netrid/v22a/nominal_behavior.md | 5 +++++ 6 files changed, 39 insertions(+), 5 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index d4a1f3b5e6..3426974426 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -159,9 +159,16 @@ def riddp_display_data() -> Tuple[str, int]: @requires_scope([Scope.Read]) def riddp_flight_details(flight_id: str) -> Tuple[str, int]: """Implements get flight details endpoint per automated testing API.""" + rid_version: RIDVersion = webapp.config[KEY_RID_VERSION] tx = db.value - if flight_id not in tx.flights: + flight_info = tx.flights.get(flight_id) + if not flight_info: return 'Flight "{}" not found'.format(flight_id), 404 - return flask.jsonify(observation_api.GetDetailsResponse()) + flight_details = fetch.flight_details(flight_info.flights_url, flight_id, True, rid_version, utm_client) + return flask.jsonify( + observation_api.GetDetailsResponse( + operator_id=flight_details.details.operator_id, + ) + ) diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index bb96bf2d24..d0e19984dd 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -321,6 +321,10 @@ def raw( def id(self) -> str: return self.raw.id + @property + def operator_id(self) -> Optional[str]: + return self.raw.operator_id + class Subscription(ImplicitDict): """Version-independent representation of a F3411 subscription.""" diff --git a/monitoring/monitorlib/rid_automated_testing/observation_api.py b/monitoring/monitorlib/rid_automated_testing/observation_api.py index e0998dc5d2..0946563aef 100644 --- a/monitoring/monitorlib/rid_automated_testing/observation_api.py +++ b/monitoring/monitorlib/rid_automated_testing/observation_api.py @@ -29,7 +29,8 @@ class Flight(ImplicitDict): class GetDetailsResponse(ImplicitDict): - pass + operator_id: Optional[str] + class GetDisplayDataResponse(ImplicitDict): diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index 4af3b7cd5a..476ef902a3 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -3,7 +3,7 @@ from typing import List, Optional from monitoring.monitorlib.fetch.rid import ( FetchedFlights, - FlightDetails, + FlightDetails ) from monitoring.uss_qualifier.common_data_definitions import Severity from monitoring.uss_qualifier.resources.netrid.evaluation import EvaluationConfiguration @@ -160,6 +160,12 @@ def evaluate_sp_details(self, details: FlightDetails, participants: List[str]): details.v22a_value.get("operator_location"), participants ) + def evaluate_dp_response(self, observed_flight_details: Optional[FlightDetails], injected_flight, participants): + self.evaluate_operator_id( + observed_flight_details.get("operator_id"), + participants, + ) + def evaluate_uas_id(self, value: Optional[v22a.api.UASID], participants: List[str]): if self._rid_version == RIDVersion.f3411_22a: formats_keys = [ diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index c88398b7d1..cabfa7d58b 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -38,7 +38,6 @@ ) from monitoring.uss_qualifier.scenarios.scenario import ( TestScenarioType, - PendingCheck, TestScenario, ) from monitoring.uss_qualifier.scenarios.astm.netrid.injection import InjectedFlight @@ -422,6 +421,18 @@ def _evaluate_normal_observation( ), ) + details, query = observer.observe_flight_details(mapping.observed_flight.id) + self._test_scenario.record_query(query) + + self._common_dictionary_evaluator.evaluate_dp_response( + details, + mapping.injected_flight, + participants=[ + observer.participant_id, + mapping.injected_flight.uss_participant_id + ], + ) + def _evaluate_flight_presence( self, observer_participant_id: str, diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md index beb41a6b49..39d29b94ba 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md @@ -192,6 +192,11 @@ Taking into account the propagation time of the injected flights, if the total n For a display area with a diagonal greather than *NetDetailsMaxDisplayAreaDiagonal* and less than *NetMaxDisplayAreaDiagonal*, **[astm.f3411.v22a.NET0480](../../../../requirements/astm/f3411/v22a.md)** requires that a Display provider shall cluster UAs in close proximity to each other using a circular or polygonal area covering no less than *NetMinClusterSize* percent of the display area size. This check validates that the display area of a cluster, measured and provided in square meters by the test harness, is no less than *NetMinClusterSize* percent of the display area. +#### Operator ID consistency with Common Dictionary check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall (NET0470) provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Operator ID, if present, is valid. (**[astm.f3411.v22a.NET0470,Table1,9](../../../../requirements/astm/f3411/v22a.md)**) + + ## Cleanup The cleanup phase of this test scenario attempts to remove injected data from all SPs. From 43a3995241173af000de4b8b38d57e2eed4f971b Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Fri, 28 Jul 2023 16:31:06 +0200 Subject: [PATCH 02/37] [rid] NET0470 UAS ID, Operator Location, Operator Altitude and Operational Status checks --- .../mock_uss/riddp/routes_observation.py | 24 +++++++++++----- monitoring/monitorlib/fetch/rid.py | 4 --- .../rid_automated_testing/observation_api.py | 6 ++-- .../netrid/common_dictionary_evaluator.py | 28 ++++++++++++------- .../astm/netrid/display_data_evaluator.py | 2 +- .../astm/netrid/v22a/nominal_behavior.md | 23 +++++++++++++++ 6 files changed, 62 insertions(+), 25 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index 3426974426..af892f2897 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -159,16 +159,26 @@ def riddp_display_data() -> Tuple[str, int]: @requires_scope([Scope.Read]) def riddp_flight_details(flight_id: str) -> Tuple[str, int]: """Implements get flight details endpoint per automated testing API.""" - rid_version: RIDVersion = webapp.config[KEY_RID_VERSION] - tx = db.value flight_info = tx.flights.get(flight_id) if not flight_info: - return 'Flight "{}" not found'.format(flight_id), 404 + return f'Flight "{flight_id}" not found', 404 + rid_version: RIDVersion = webapp.config[KEY_RID_VERSION] flight_details = fetch.flight_details(flight_info.flights_url, flight_id, True, rid_version, utm_client) - return flask.jsonify( - observation_api.GetDetailsResponse( - operator_id=flight_details.details.operator_id, + if rid_version == RIDVersion.f3411_19: + # TODO: Implement details for F3411-19 + return flask.jsonify(observation_api.GetDetailsResponse(id=flight_id)) + elif rid_version == RIDVersion.f3411_22a: + result = observation_api.GetDetailsResponse( + id=flight_id, + operator_id=flight_details.details.v22a_value.get("operator_id"), + uas_id=flight_details.details.v22a_value.get("uas_id"), + operator_location=flight_details.details.v22a_value.get("operator_location"), + operational_status=flight_details.details.v22a_value.get("operational_status"), + ) + return flask.jsonify( + result ) - ) + else: + return f"Support for RID version {rid_version} not yet implemented", 501 diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index d0e19984dd..bb96bf2d24 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -321,10 +321,6 @@ def raw( def id(self) -> str: return self.raw.id - @property - def operator_id(self) -> Optional[str]: - return self.raw.operator_id - class Subscription(ImplicitDict): """Version-independent representation of a F3411 subscription.""" diff --git a/monitoring/monitorlib/rid_automated_testing/observation_api.py b/monitoring/monitorlib/rid_automated_testing/observation_api.py index 0946563aef..978d8c5d23 100644 --- a/monitoring/monitorlib/rid_automated_testing/observation_api.py +++ b/monitoring/monitorlib/rid_automated_testing/observation_api.py @@ -1,6 +1,7 @@ from typing import List, Optional from implicitdict import ImplicitDict +from uas_standards.astm.f3411 import v22a # Mirrors of types defined in remote ID automated testing observation API @@ -28,9 +29,8 @@ class Flight(ImplicitDict): recent_paths: Optional[List[Path]] -class GetDetailsResponse(ImplicitDict): - operator_id: Optional[str] - +class GetDetailsResponse(v22a.api.RIDFlightDetails): + pass class GetDisplayDataResponse(ImplicitDict): diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index 476ef902a3..ddf45e9cfc 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -1,18 +1,21 @@ import datetime -import s2sphere from typing import List, Optional +import s2sphere + +from uas_standards.ansi_cta_2063_a import SerialNumber +from uas_standards.astm.f3411 import v22a + from monitoring.monitorlib.fetch.rid import ( - FetchedFlights, - FlightDetails + FetchedFlights ) +from monitoring.monitorlib.fetch.rid import FlightDetails +from monitoring.monitorlib.geo import validate_lat, validate_lng +from monitoring.monitorlib.rid import RIDVersion from monitoring.uss_qualifier.common_data_definitions import Severity from monitoring.uss_qualifier.resources.netrid.evaluation import EvaluationConfiguration from monitoring.uss_qualifier.scenarios.scenario import TestScenarioType, PendingCheck -from monitoring.monitorlib.rid import RIDVersion -from monitoring.monitorlib.geo import validate_lat, validate_lng from monitoring.monitorlib.fetch.rid import Flight, Position -from uas_standards.ansi_cta_2063_a import SerialNumber -from uas_standards.astm.f3411 import v22a + # SP responses to /flights endpoint's p99 should be below this: SP_FLIGHTS_RESPONSE_TIME_TOLERANCE_SEC = 3 @@ -160,10 +163,15 @@ def evaluate_sp_details(self, details: FlightDetails, participants: List[str]): details.v22a_value.get("operator_location"), participants ) - def evaluate_dp_response(self, observed_flight_details: Optional[FlightDetails], injected_flight, participants): + def evaluate_dp_details(self, observed_details: Optional[FlightDetails], injected_flight, participants): self.evaluate_operator_id( - observed_flight_details.get("operator_id"), - participants, + observed_details.get("operator_id"), participants + ) + self.evaluate_uas_id( + observed_details.get("uas_id"), participants + ) + self.evaluate_operator_location( + observed_details.get("operator_location"), participants ) def evaluate_uas_id(self, value: Optional[v22a.api.UASID], participants: List[str]): diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index cabfa7d58b..352d496905 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -424,7 +424,7 @@ def _evaluate_normal_observation( details, query = observer.observe_flight_details(mapping.observed_flight.id) self._test_scenario.record_query(query) - self._common_dictionary_evaluator.evaluate_dp_response( + self._common_dictionary_evaluator.evaluate_dp_details( details, mapping.injected_flight, participants=[ diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md index 39d29b94ba..8d61b8db1f 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md @@ -192,10 +192,33 @@ Taking into account the propagation time of the injected flights, if the total n For a display area with a diagonal greather than *NetDetailsMaxDisplayAreaDiagonal* and less than *NetMaxDisplayAreaDiagonal*, **[astm.f3411.v22a.NET0480](../../../../requirements/astm/f3411/v22a.md)** requires that a Display provider shall cluster UAs in close proximity to each other using a circular or polygonal area covering no less than *NetMinClusterSize* percent of the display area size. This check validates that the display area of a cluster, measured and provided in square meters by the test harness, is no less than *NetMinClusterSize* percent of the display area. +#### UAS ID presence in flight details check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the UAS ID is present in the information sent by the Display Provider. (**[astm.f3411.v22a.NET0470,Table1,1](../../../../requirements/astm/f3411/v22a.md)**) + +#### UAS ID (Serial Number format) consistency with Common Dictionary check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the UAS ID is in serial number format. (**[astm.f3411.v22a.NET0470,Table1,1a](../../../../requirements/astm/f3411/v22a.md)**) + +#### Operational Status consistency with Common Dictionary check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Operational Status, if present, is valid. (**[astm.f3411.v22a.NET0470,Table1,7](../../../../requirements/astm/f3411/v22a.md)**) + #### Operator ID consistency with Common Dictionary check **[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall (NET0470) provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Operator ID, if present, is valid. (**[astm.f3411.v22a.NET0470,Table1,9](../../../../requirements/astm/f3411/v22a.md)**) +#### Operator Location consistency with Common Dictionary check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Operator Latitude (**[astm.f3411.v22a.NET0470,Table1,23](../../../../requirements/astm/f3411/v22a.md)**) and Longitude (**[astm.f3411.v22a.NET0470,Table1,24](../../../../requirements/astm/f3411/v22a.md)**), if present, are valid. + +#### Operator Altitude consistency with Common Dictionary check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that if the Operator Altitude is based on WGS-84 height above ellipsoid (HAE), is provided in meters and must have a minimum resolution of 1 m. (**[astm.f3411.v22a.NET0470,Table1,25](../../../../requirements/astm/f3411/v22a.md)**) + +#### Operator Altitude Type consistency with Common Dictionary check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that if the Operator Altitude Type is valid, if present. (**[astm.f3411.v22a.NET0470,Table1,26](../../../../requirements/astm/f3411/v22a.md)**) ## Cleanup From 02d1209a7482fea0863f7e5559afeb9f9b35bcde Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Fri, 4 Aug 2023 17:51:36 +0200 Subject: [PATCH 03/37] Fix misused argument --- .../astm/netrid/common_dictionary_evaluator_test.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py index ffdde3cdd0..3ca3bb0337 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py @@ -24,7 +24,7 @@ def step_under_test(self: UnitTestScenario): test_scenario=self, rid_version=RIDVersion.f3411_22a, ) - evaluator.evaluate_operator_id(value, RIDVersion.f3411_22a) + evaluator.evaluate_operator_id(value, []) unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome @@ -47,7 +47,7 @@ def step_under_test(self: UnitTestScenario): test_scenario=self, rid_version=RIDVersion.f3411_22a, ) - evaluator.evaluate_operator_location(value, RIDVersion.f3411_22a) + evaluator.evaluate_operator_location(value, []) unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert ( @@ -177,7 +177,7 @@ def step_under_test(self: UnitTestScenario): rid_version=RIDVersion.f3411_22a, ) - evaluator.evaluate_operational_status(value, RIDVersion.f3411_22a) + evaluator.evaluate_operational_status(value, []) unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome From 4b90cb0bd65c659509bd5e4d33e419348952e8da Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Fri, 4 Aug 2023 17:59:58 +0200 Subject: [PATCH 04/37] format --- .../mock_uss/riddp/routes_observation.py | 22 +++++++++++-------- .../netrid/common_dictionary_evaluator.py | 16 ++++++-------- .../astm/netrid/display_data_evaluator.py | 2 +- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index af892f2897..98534723b7 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -165,20 +165,24 @@ def riddp_flight_details(flight_id: str) -> Tuple[str, int]: return f'Flight "{flight_id}" not found', 404 rid_version: RIDVersion = webapp.config[KEY_RID_VERSION] - flight_details = fetch.flight_details(flight_info.flights_url, flight_id, True, rid_version, utm_client) + flight_details = fetch.flight_details( + flight_info.flights_url, flight_id, True, rid_version, utm_client + ) if rid_version == RIDVersion.f3411_19: # TODO: Implement details for F3411-19 return flask.jsonify(observation_api.GetDetailsResponse(id=flight_id)) elif rid_version == RIDVersion.f3411_22a: result = observation_api.GetDetailsResponse( - id=flight_id, - operator_id=flight_details.details.v22a_value.get("operator_id"), - uas_id=flight_details.details.v22a_value.get("uas_id"), - operator_location=flight_details.details.v22a_value.get("operator_location"), - operational_status=flight_details.details.v22a_value.get("operational_status"), - ) - return flask.jsonify( - result + id=flight_id, + operator_id=flight_details.details.v22a_value.get("operator_id"), + uas_id=flight_details.details.v22a_value.get("uas_id"), + operator_location=flight_details.details.v22a_value.get( + "operator_location" + ), + operational_status=flight_details.details.v22a_value.get( + "operational_status" + ), ) + return flask.jsonify(result) else: return f"Support for RID version {rid_version} not yet implemented", 501 diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index ddf45e9cfc..053bb107e9 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -5,10 +5,10 @@ from uas_standards.ansi_cta_2063_a import SerialNumber from uas_standards.astm.f3411 import v22a +from monitoring.monitorlib.fetch.rid import FetchedFlights from monitoring.monitorlib.fetch.rid import ( - FetchedFlights + FlightDetails, ) -from monitoring.monitorlib.fetch.rid import FlightDetails from monitoring.monitorlib.geo import validate_lat, validate_lng from monitoring.monitorlib.rid import RIDVersion from monitoring.uss_qualifier.common_data_definitions import Severity @@ -163,13 +163,11 @@ def evaluate_sp_details(self, details: FlightDetails, participants: List[str]): details.v22a_value.get("operator_location"), participants ) - def evaluate_dp_details(self, observed_details: Optional[FlightDetails], injected_flight, participants): - self.evaluate_operator_id( - observed_details.get("operator_id"), participants - ) - self.evaluate_uas_id( - observed_details.get("uas_id"), participants - ) + def evaluate_dp_details( + self, observed_details: Optional[FlightDetails], injected_flight, participants + ): + self.evaluate_operator_id(observed_details.get("operator_id"), participants) + self.evaluate_uas_id(observed_details.get("uas_id"), participants) self.evaluate_operator_location( observed_details.get("operator_location"), participants ) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index 352d496905..d51d6031fb 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -429,7 +429,7 @@ def _evaluate_normal_observation( mapping.injected_flight, participants=[ observer.participant_id, - mapping.injected_flight.uss_participant_id + mapping.injected_flight.uss_participant_id, ], ) From 23eb42e487e81a52d60c45a54ae2e8b684339ded Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Fri, 4 Aug 2023 18:55:27 +0200 Subject: [PATCH 05/37] Reorganize version checks in common_dictionary --- .../netrid/common_dictionary_evaluator.py | 33 ++++++++++--------- .../common_dictionary_evaluator_test.py | 6 ++-- .../astm/netrid/display_data_evaluator.py | 1 - 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index 053bb107e9..6a99bbf4c8 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -63,6 +63,12 @@ def evaluate_sp_flights( f.v22a_value.get("current_state", {}).get("operational_status"), participants, ) + for f in observed_flights.flights: + self._evaluate_operational_status( + f.raw.get("current_state", {}).get("operational_status"), + participants, + ) + def _evaluate_recent_position_time( self, p: Position, query_time: datetime.datetime, check: PendingCheck @@ -154,25 +160,20 @@ def fail_check(): fail_check() def evaluate_sp_details(self, details: FlightDetails, participants: List[str]): - if self._rid_version == RIDVersion.f3411_22a: - self.evaluate_uas_id(details.v22a_value.get("uas_id"), participants) - self.evaluate_operator_id( - details.v22a_value.get("operator_id"), participants - ) - self.evaluate_operator_location( - details.v22a_value.get("operator_location"), participants - ) + self._evaluate_uas_id(details.raw.get("uas_id"), participants) + self._evaluate_operator_id(details.raw.get("operator_id"), participants) + self._evaluate_operator_location(details.raw.get("operator_location"), participants) def evaluate_dp_details( - self, observed_details: Optional[FlightDetails], injected_flight, participants + self, observed_details: Optional[FlightDetails], participants: List[str] ): - self.evaluate_operator_id(observed_details.get("operator_id"), participants) - self.evaluate_uas_id(observed_details.get("uas_id"), participants) - self.evaluate_operator_location( + self._evaluate_operator_id(observed_details.get("operator_id"), participants) + self._evaluate_uas_id(observed_details.get("uas_id"), participants) + self._evaluate_operator_location( observed_details.get("operator_location"), participants ) - def evaluate_uas_id(self, value: Optional[v22a.api.UASID], participants: List[str]): + def _evaluate_uas_id(self, value: Optional[v22a.api.UASID], participants: List[str]): if self._rid_version == RIDVersion.f3411_22a: formats_keys = [ "serial_number", @@ -219,7 +220,7 @@ def evaluate_uas_id(self, value: Optional[v22a.api.UASID], participants: List[st message=f"Unsupported version {self._rid_version}: skipping UAS ID evaluation", ) - def evaluate_operator_id(self, value: Optional[str], participants: List[str]): + def _evaluate_operator_id(self, value: Optional[str], participants: List[str]): if self._rid_version == RIDVersion.f3411_22a: if value: with self._test_scenario.check( @@ -237,7 +238,7 @@ def evaluate_operator_id(self, value: Optional[str], participants: List[str]): message=f"Unsupported version {self._rid_version}: skipping Operator ID evaluation", ) - def evaluate_operator_location( + def _evaluate_operator_location( self, value: Optional[v22a.api.OperatorLocation], participants: List[str] ): if self._rid_version == RIDVersion.f3411_22a: @@ -312,7 +313,7 @@ def evaluate_operator_location( message=f"Unsupported version {self._rid_version}: skipping Operator Location evaluation", ) - def evaluate_operational_status( + def _evaluate_operational_status( self, value: Optional[str], participants: List[str] ): if self._rid_version == RIDVersion.f3411_22a: diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py index 3ca3bb0337..a92b923f16 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py @@ -24,7 +24,7 @@ def step_under_test(self: UnitTestScenario): test_scenario=self, rid_version=RIDVersion.f3411_22a, ) - evaluator.evaluate_operator_id(value, []) + evaluator._evaluate_operator_id(value, []) unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome @@ -47,7 +47,7 @@ def step_under_test(self: UnitTestScenario): test_scenario=self, rid_version=RIDVersion.f3411_22a, ) - evaluator.evaluate_operator_location(value, []) + evaluator._evaluate_operator_location(value, []) unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert ( @@ -177,7 +177,7 @@ def step_under_test(self: UnitTestScenario): rid_version=RIDVersion.f3411_22a, ) - evaluator.evaluate_operational_status(value, []) + evaluator._evaluate_operational_status(value, []) unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index d51d6031fb..47bd239863 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -426,7 +426,6 @@ def _evaluate_normal_observation( self._common_dictionary_evaluator.evaluate_dp_details( details, - mapping.injected_flight, participants=[ observer.participant_id, mapping.injected_flight.uss_participant_id, From dd73352ec1998f31523cbcbbb4816da876db9dc9 Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Fri, 4 Aug 2023 19:01:27 +0200 Subject: [PATCH 06/37] Add todo for location altitude to address it separately --- .../resources/netrid/simulation/operator_flight_details.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monitoring/uss_qualifier/resources/netrid/simulation/operator_flight_details.py b/monitoring/uss_qualifier/resources/netrid/simulation/operator_flight_details.py index a07a95aef8..ba56001204 100644 --- a/monitoring/uss_qualifier/resources/netrid/simulation/operator_flight_details.py +++ b/monitoring/uss_qualifier/resources/netrid/simulation/operator_flight_details.py @@ -37,6 +37,7 @@ def generate_operation_description(self): return self.random.choice(operation_description) def generate_operator_location(self, centroid): + # TODO: Inject operator location altitude operator_location = LatLngPoint(lat=centroid.y, lng=centroid.x) return operator_location From 3e5e18c8a45400822863fbb445eac741b31f58a5 Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Fri, 4 Aug 2023 19:11:56 +0200 Subject: [PATCH 07/37] Format and fix type of observed_details --- .../astm/netrid/common_dictionary_evaluator.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index 6a99bbf4c8..a1a783fc5a 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -2,11 +2,14 @@ from typing import List, Optional import s2sphere +from monitoring.monitorlib.rid_automated_testing.observation_api import ( + GetDetailsResponse, +) + from uas_standards.ansi_cta_2063_a import SerialNumber from uas_standards.astm.f3411 import v22a - -from monitoring.monitorlib.fetch.rid import FetchedFlights from monitoring.monitorlib.fetch.rid import ( + FetchedFlights, FlightDetails, ) from monitoring.monitorlib.geo import validate_lat, validate_lng @@ -162,10 +165,12 @@ def fail_check(): def evaluate_sp_details(self, details: FlightDetails, participants: List[str]): self._evaluate_uas_id(details.raw.get("uas_id"), participants) self._evaluate_operator_id(details.raw.get("operator_id"), participants) - self._evaluate_operator_location(details.raw.get("operator_location"), participants) + self._evaluate_operator_location( + details.raw.get("operator_location"), participants + ) def evaluate_dp_details( - self, observed_details: Optional[FlightDetails], participants: List[str] + self, observed_details: Optional[GetDetailsResponse], participants: List[str] ): self._evaluate_operator_id(observed_details.get("operator_id"), participants) self._evaluate_uas_id(observed_details.get("uas_id"), participants) @@ -173,7 +178,9 @@ def evaluate_dp_details( observed_details.get("operator_location"), participants ) - def _evaluate_uas_id(self, value: Optional[v22a.api.UASID], participants: List[str]): + def _evaluate_uas_id( + self, value: Optional[v22a.api.UASID], participants: List[str] + ): if self._rid_version == RIDVersion.f3411_22a: formats_keys = [ "serial_number", From 106db1e0078fab20c7452cbb7992bec314d3038b Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Fri, 4 Aug 2023 19:24:03 +0200 Subject: [PATCH 08/37] Capture TODO --- monitoring/monitorlib/rid_automated_testing/observation_api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/monitoring/monitorlib/rid_automated_testing/observation_api.py b/monitoring/monitorlib/rid_automated_testing/observation_api.py index 978d8c5d23..6ae120ac71 100644 --- a/monitoring/monitorlib/rid_automated_testing/observation_api.py +++ b/monitoring/monitorlib/rid_automated_testing/observation_api.py @@ -30,6 +30,7 @@ class Flight(ImplicitDict): class GetDetailsResponse(v22a.api.RIDFlightDetails): + # TODO: Update automated_testing_interface instead of using the ASTM details response schema pass From 4fd0a8b517806cd90c01635581c75cdb53245a47 Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Wed, 13 Sep 2023 16:08:51 +0200 Subject: [PATCH 09/37] Implement new observation api fields for mock details responses --- .../mock_uss/riddp/routes_observation.py | 28 ++-- monitoring/monitorlib/fetch/rid.py | 50 ++++++ .../resources/netrid/observers.py | 5 +- .../netrid/common_dictionary_evaluator.py | 152 +++++++++++------- 4 files changed, 164 insertions(+), 71 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index 98534723b7..8286671791 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -1,5 +1,5 @@ from typing import Dict, List, Optional, Tuple - +from datetime import datetime import arrow import flask from loguru import logger @@ -11,7 +11,7 @@ from monitoring.monitorlib.fetch import rid as fetch from monitoring.monitorlib.fetch.rid import Flight, FetchedISAs from monitoring.monitorlib.rid import RIDVersion -from monitoring.monitorlib.rid_automated_testing import observation_api +from uas_standards.interuss.automated_testing.rid.v1 import observation as observation_api from monitoring.mock_uss import webapp from monitoring.mock_uss.auth import requires_scope from . import clustering, database, utm_client @@ -55,10 +55,17 @@ def _make_flight_observation( paths.append(current_path) p = flight.most_recent_position + current_state = observation_api.CurrentState( + timestamp=p.time.isoformat(), + operational_status=None, # TODO: Propagate value + track=None, # TODO: Propagate value + speed=None # TODO: Propagate value + ) return observation_api.Flight( id=flight.id, most_recent_position=observation_api.Position(lat=p.lat, lng=p.lng, alt=p.alt), recent_paths=[observation_api.Path(positions=path) for path in paths], + current_state=current_state ) @@ -172,15 +179,18 @@ def riddp_flight_details(flight_id: str) -> Tuple[str, int]: # TODO: Implement details for F3411-19 return flask.jsonify(observation_api.GetDetailsResponse(id=flight_id)) elif rid_version == RIDVersion.f3411_22a: + details = flight_details.details result = observation_api.GetDetailsResponse( - id=flight_id, - operator_id=flight_details.details.v22a_value.get("operator_id"), - uas_id=flight_details.details.v22a_value.get("uas_id"), - operator_location=flight_details.details.v22a_value.get( - "operator_location" + operator=observation_api.Operator( + id=details.operator_id, + location=details.operator_location.get("position"), + altitude=observation_api.OperatorAltitude( + altitude=details.operator_location.get("altitude"), + altitude_type=details.operator_location.get("altitude_type"), + ), ), - operational_status=flight_details.details.v22a_value.get( - "operational_status" + uas=observation_api.UAS( + id=details.plain_uas_id, ), ) return flask.jsonify(result) diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index bb96bf2d24..05c8c61d67 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -321,6 +321,56 @@ def raw( def id(self) -> str: return self.raw.id + @property + def operator_id(self) -> str: + if self.rid_version == RIDVersion.f3411_19: + return self.v19_value.operator_id + elif self.rid_version == RIDVersion.f3411_22a: + return self.v22a_value.operator_id + else: + raise NotImplementedError( + f"Cannot retrieve operator_id using RID version {self.rid_version}" + ) + + @property + def plain_uas_id(self) -> Optional[str]: + """Returns a UAS id as a plain string without type hint. + If multiple are provided: + For v19, registration_number is returned if set, else it falls back to the serial_number. + For v20, the order of ASTM F3411-v19 Table 1 is used. + If no match, it returns None. + """ + if self.rid_version == RIDVersion.f3411_19: + rn = self.v19_value.registration_number + if rn: + return rn + else: + return self.v19_value.serial_number + elif self.rid_version == RIDVersion.f3411_22a: + uas_id = self.v22a_value.uas_id + if uas_id.serial_number: + return uas_id.serial_number + elif uas_id.registration_id: + return uas_id.registration_id + elif uas_id.utm_id: + return uas_id.utm_id + elif uas_id.specific_session_id: + return uas_id.specific_session_id + else: + raise NotImplementedError( + f"Cannot retrieve plain_uas_id using RID version {self.rid_version}" + ) + + @property + def operator_location(self) -> v22a.api.OperatorLocation: + if self.rid_version == RIDVersion.f3411_19: + return v22a.api.OperatorLocation(position=self.v19_value.operator_location) + elif self.rid_version == RIDVersion.f3411_22a: + return self.v22a_value.operator_location + else: + raise NotImplementedError( + f"Cannot retrieve operator_location using RID version {self.rid_version}" + ) class Subscription(ImplicitDict): """Version-independent representation of a F3411 subscription.""" diff --git a/monitoring/uss_qualifier/resources/netrid/observers.py b/monitoring/uss_qualifier/resources/netrid/observers.py index e29d5ddf76..869587301b 100644 --- a/monitoring/uss_qualifier/resources/netrid/observers.py +++ b/monitoring/uss_qualifier/resources/netrid/observers.py @@ -8,7 +8,7 @@ from monitoring.monitorlib.fetch import QueryType from monitoring.monitorlib.infrastructure import UTMClientSession from monitoring.monitorlib.rid import RIDVersion -from monitoring.monitorlib.rid_automated_testing import observation_api +from uas_standards.interuss.automated_testing.rid.v1 import observation as observation_api from monitoring.uss_qualifier.resources.resource import Resource from monitoring.uss_qualifier.resources.communications import AuthAdapterResource @@ -85,7 +85,8 @@ def observe_flight_details( if query.status_code == 200 else None ) - except ValueError: + except ValueError as e: + logger.error("Error parsing observation details response: {}", e) result = None return result, query diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index a1a783fc5a..61e9014b9e 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -2,8 +2,8 @@ from typing import List, Optional import s2sphere -from monitoring.monitorlib.rid_automated_testing.observation_api import ( - GetDetailsResponse, +from uas_standards.interuss.automated_testing.rid.v1.observation import ( + GetDetailsResponse, OperatorAltitudeAltitudeType, ) from uas_standards.ansi_cta_2063_a import SerialNumber @@ -12,7 +12,7 @@ FetchedFlights, FlightDetails, ) -from monitoring.monitorlib.geo import validate_lat, validate_lng +from monitoring.monitorlib.geo import validate_lat, validate_lng, Altitude, LatLngPoint from monitoring.monitorlib.rid import RIDVersion from monitoring.uss_qualifier.common_data_definitions import Severity from monitoring.uss_qualifier.resources.netrid.evaluation import EvaluationConfiguration @@ -62,7 +62,7 @@ def evaluate_sp_flights( if self._rid_version == RIDVersion.f3411_22a: for f in observed_flights.flights: # Evaluate on all flights regardless of where they came from - self.evaluate_operational_status( + self._evaluate_operational_status( f.v22a_value.get("current_state", {}).get("operational_status"), participants, ) @@ -164,18 +164,27 @@ def fail_check(): def evaluate_sp_details(self, details: FlightDetails, participants: List[str]): self._evaluate_uas_id(details.raw.get("uas_id"), participants) - self._evaluate_operator_id(details.raw.get("operator_id"), participants) + self._evaluate_operator_id(details.operator_id, participants) self._evaluate_operator_location( - details.raw.get("operator_location"), participants + details.operator_location.position, + details.operator_location.get("altitude"), + details.operator_location.get("altitude_type"), + participants ) def evaluate_dp_details( self, observed_details: Optional[GetDetailsResponse], participants: List[str] ): - self._evaluate_operator_id(observed_details.get("operator_id"), participants) - self._evaluate_uas_id(observed_details.get("uas_id"), participants) + logger.info(observed_details) + if not observed_details: + observed_details = {} + self._evaluate_plain_uas_id(observed_details.get("uas", {}).get("id"), participants) + self._evaluate_operator_id(observed_details.get("operator", {}).get("id"), participants) self._evaluate_operator_location( - observed_details.get("operator_location"), participants + observed_details.get("operator", {}).get("location"), + observed_details.get("operator", {}).get("altitude"), + observed_details.get("operator", {}).get("altitude_type"), + participants ) def _evaluate_uas_id( @@ -198,7 +207,7 @@ def _evaluate_uas_id( ) as check: if formats_count == 0: check.record_failed( - "UAS ID not present as required by the Common Dictionary definition", + f"UAS ID not present as required by the Common Dictionary definition: {value}", severity=Severity.Medium, ) return @@ -227,6 +236,30 @@ def _evaluate_uas_id( message=f"Unsupported version {self._rid_version}: skipping UAS ID evaluation", ) + def _evaluate_plain_uas_id( + self, value: str, participants: List[str] + ): + with self._test_scenario.check( + "UAS ID presence in flight details", participants + ) as check: + if not value: + check.record_failed( + f"UAS ID not present as required by the Common Dictionary definition: {value}", + severity=Severity.Medium, + ) + return + + if SerialNumber(value).valid: + self._test_scenario.check( + "UAS ID (Serial Number format) consistency with Common Dictionary", + participants, + ).record_passed(participants) + + # TODO: Add registration id format check + # TODO: Add utm id format check + # TODO: Add specific session id format check + # TODO: Add a check to validate at least one format is correct + def _evaluate_operator_id(self, value: Optional[str], participants: List[str]): if self._rid_version == RIDVersion.f3411_22a: if value: @@ -246,73 +279,72 @@ def _evaluate_operator_id(self, value: Optional[str], participants: List[str]): ) def _evaluate_operator_location( - self, value: Optional[v22a.api.OperatorLocation], participants: List[str] + self, position: Optional[LatLngPoint], altitude: Optional[Altitude], altitude_type: Optional[OperatorAltitudeAltitudeType], participants: List[str] ): if self._rid_version == RIDVersion.f3411_22a: - if value: + with self._test_scenario.check( + "Operator Location consistency with Common Dictionary", participants + ) as check: + lat = position.lat + try: + lat = validate_lat(lat) + except ValueError: + check.record_failed( + "Operator Location contains an invalid latitude", + details=f"Invalid latitude: {lat}", + severity=Severity.Medium, + ) + lng = position.lng + try: + lng = validate_lng(lng) + except ValueError: + check.record_failed( + "Operator Location contains an invalid longitude", + details=f"Invalid longitude: {lng}", + severity=Severity.Medium, + ) + + alt = altitude + if alt: with self._test_scenario.check( - "Operator Location consistency with Common Dictionary", participants + "Operator Altitude consistency with Common Dictionary", + participants, ) as check: - lat = value.position.lat - try: - lat = validate_lat(lat) - except ValueError: + if alt.reference != v22a.api.AltitudeReference.W84: check.record_failed( - "Operator Location contains an invalid latitude", - details=f"Invalid latitude: {lat}", + "Operator Altitude shall be based on WGS-84 height above ellipsoid (HAE)", + details=f"Invalid Operator Altitude reference: {alt.reference}", severity=Severity.Medium, ) - lng = value.position.lng - try: - lng = validate_lng(lng) - except ValueError: + if alt.units != v22a.api.AltitudeUnits.M: check.record_failed( - "Operator Location contains an invalid longitude", - details=f"Invalid longitude: {lng}", + "Operator Altitude units shall be provided in meters", + details=f"Invalid Operator Altitude units: {alt.units}", + severity=Severity.Medium, + ) + if alt.value != round(alt.value): + check.record_failed( + "Operator Altitude must have a minimum resolution of 1 m.", + details=f"Invalid Operator Altitude: {alt.value}", severity=Severity.Medium, ) - alt = value.get("altitude") - if alt: + alt_type = altitude_type + if alt_type: with self._test_scenario.check( - "Operator Altitude consistency with Common Dictionary", + "Operator Altitude Type consistency with Common Dictionary", participants, ) as check: - if alt.reference != v22a.api.AltitudeReference.W84: - check.record_failed( - "Operator Altitude shall be based on WGS-84 height above ellipsoid (HAE)", - details=f"Invalid Operator Altitude reference: {alt.reference}", - severity=Severity.Medium, - ) - if alt.units != v22a.api.AltitudeUnits.M: + try: + v22a.api.OperatorLocationAltitude_type( + alt_type + ) # raise ValueError if alt_type is invalid + except ValueError: check.record_failed( - "Operator Altitude units shall be provided in meters", - details=f"Invalid Operator Altitude units: {alt.units}", + "Operator Location contains an altitude type which is invalid", + details=f"Invalid altitude type: {alt_type}", severity=Severity.Medium, ) - if alt.value != round(alt.value): - check.record_failed( - "Operator Altitude must have a minimum resolution of 1 m.", - details=f"Invalid Operator Altitude: {alt.value}", - severity=Severity.Medium, - ) - - alt_type = value.get("altitude_type") - if alt_type: - with self._test_scenario.check( - "Operator Altitude Type consistency with Common Dictionary", - participants, - ) as check: - try: - v22a.api.OperatorLocationAltitudeType( - alt_type - ) # raise ValueError if alt_type is invalid - except ValueError: - check.record_failed( - "Operator Location contains an altitude type which is invalid", - details=f"Invalid altitude type: {alt_type}", - severity=Severity.Medium, - ) else: self._test_scenario.record_note( From 65d5a5e00e38bc8625dc86bd89c2a07f25acb550 Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Wed, 13 Sep 2023 18:36:45 +0200 Subject: [PATCH 10/37] Fix unit tests and format --- monitoring/monitorlib/geo.py | 8 ++ .../netrid/common_dictionary_evaluator.py | 86 +++++++----- .../common_dictionary_evaluator_test.py | 129 ++++++++++-------- 3 files changed, 131 insertions(+), 92 deletions(-) diff --git a/monitoring/monitorlib/geo.py b/monitoring/monitorlib/geo.py index dc191d5a6a..d3543455b8 100644 --- a/monitoring/monitorlib/geo.py +++ b/monitoring/monitorlib/geo.py @@ -50,6 +50,14 @@ class Altitude(ImplicitDict): reference: AltitudeDatum units: DistanceUnits + @staticmethod + def w84m(value: float): + return Altitude( + value=value, + reference=AltitudeDatum.W84, + units=DistanceUnits.M + ) + class Volume3D(ImplicitDict): outline_circle: Optional[Circle] = None diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index 61e9014b9e..cdfcb68a8b 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -3,7 +3,8 @@ import s2sphere from uas_standards.interuss.automated_testing.rid.v1.observation import ( - GetDetailsResponse, OperatorAltitudeAltitudeType, + GetDetailsResponse, + OperatorAltitudeAltitudeType, ) from uas_standards.ansi_cta_2063_a import SerialNumber @@ -72,7 +73,6 @@ def evaluate_sp_flights( participants, ) - def _evaluate_recent_position_time( self, p: Position, query_time: datetime.datetime, check: PendingCheck ): @@ -169,22 +169,32 @@ def evaluate_sp_details(self, details: FlightDetails, participants: List[str]): details.operator_location.position, details.operator_location.get("altitude"), details.operator_location.get("altitude_type"), - participants + participants, ) def evaluate_dp_details( self, observed_details: Optional[GetDetailsResponse], participants: List[str] ): - logger.info(observed_details) if not observed_details: observed_details = {} - self._evaluate_plain_uas_id(observed_details.get("uas", {}).get("id"), participants) - self._evaluate_operator_id(observed_details.get("operator", {}).get("id"), participants) + + self._evaluate_plain_uas_id( + observed_details.get("uas", {}).get("id"), participants + ) + + operator = observed_details.get("operator", {}) + self._evaluate_operator_id(operator.get("id"), participants) + + operator_location = operator.get("location", {}) + operator_altitude = operator.get("altitude", {}) + operator_altitude_value = operator_altitude.get("altitude") self._evaluate_operator_location( - observed_details.get("operator", {}).get("location"), - observed_details.get("operator", {}).get("altitude"), - observed_details.get("operator", {}).get("altitude_type"), - participants + operator_location, + Altitude.w84m(value=operator_altitude_value) + if operator_altitude_value + else None, + operator_altitude.get("altitude_type"), + participants, ) def _evaluate_uas_id( @@ -236,29 +246,27 @@ def _evaluate_uas_id( message=f"Unsupported version {self._rid_version}: skipping UAS ID evaluation", ) - def _evaluate_plain_uas_id( - self, value: str, participants: List[str] - ): - with self._test_scenario.check( - "UAS ID presence in flight details", participants - ) as check: - if not value: - check.record_failed( - f"UAS ID not present as required by the Common Dictionary definition: {value}", - severity=Severity.Medium, - ) - return + def _evaluate_plain_uas_id(self, value: str, participants: List[str]): + with self._test_scenario.check( + "UAS ID presence in flight details", participants + ) as check: + if not value: + check.record_failed( + f"UAS ID not present as required by the Common Dictionary definition: {value}", + severity=Severity.Medium, + ) + return - if SerialNumber(value).valid: - self._test_scenario.check( - "UAS ID (Serial Number format) consistency with Common Dictionary", - participants, - ).record_passed(participants) + if SerialNumber(value).valid: + self._test_scenario.check( + "UAS ID (Serial Number format) consistency with Common Dictionary", + participants, + ).record_passed(participants) - # TODO: Add registration id format check - # TODO: Add utm id format check - # TODO: Add specific session id format check - # TODO: Add a check to validate at least one format is correct + # TODO: Add registration id format check + # TODO: Add utm id format check + # TODO: Add specific session id format check + # TODO: Add a check to validate at least one format is correct def _evaluate_operator_id(self, value: Optional[str], participants: List[str]): if self._rid_version == RIDVersion.f3411_22a: @@ -279,12 +287,24 @@ def _evaluate_operator_id(self, value: Optional[str], participants: List[str]): ) def _evaluate_operator_location( - self, position: Optional[LatLngPoint], altitude: Optional[Altitude], altitude_type: Optional[OperatorAltitudeAltitudeType], participants: List[str] + self, + position: Optional[LatLngPoint], + altitude: Optional[Altitude], + altitude_type: Optional[OperatorAltitudeAltitudeType], + participants: List[str], ): if self._rid_version == RIDVersion.f3411_22a: with self._test_scenario.check( "Operator Location consistency with Common Dictionary", participants ) as check: + if not position: + check.record_failed( + "Missing Operator position", + details=f"Invalid position: {position}", + severity=Severity.Medium, + ) + return + lat = position.lat try: lat = validate_lat(lat) @@ -336,7 +356,7 @@ def _evaluate_operator_location( participants, ) as check: try: - v22a.api.OperatorLocationAltitude_type( + v22a.api.OperatorLocationAltitudeType( alt_type ) # raise ValueError if alt_type is invalid except ValueError: diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py index a92b923f16..7c34c6a964 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py @@ -1,6 +1,10 @@ from datetime import datetime, timedelta, timezone import s2sphere -from typing import List, Tuple +from typing import List, Tuple, Optional +from uas_standards.interuss.automated_testing.rid.v1.observation import ( + OperatorAltitudeAltitudeType, +) + from monitoring.monitorlib.rid import RIDVersion from monitoring.monitorlib.fetch.rid import Flight from monitoring.uss_qualifier.scenarios.astm.netrid.common_dictionary_evaluator import ( @@ -39,7 +43,7 @@ def test_operator_id_ascii(): def _assert_operator_location( - value: OperatorLocation, expected_passed_checks, expected_failed_checks + position, altitude, altitude_type, expected_passed_checks, expected_failed_checks ): def step_under_test(self: UnitTestScenario): evaluator = RIDCommonDictionaryEvaluator( @@ -47,7 +51,7 @@ def step_under_test(self: UnitTestScenario): test_scenario=self, rid_version=RIDVersion.f3411_22a, ) - evaluator._evaluate_operator_location(value, []) + evaluator._evaluate_operator_location(position, altitude, altitude_type, []) unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert ( @@ -61,106 +65,113 @@ def step_under_test(self: UnitTestScenario): def test_operator_location(): - valid_locations: List[Tuple[OperatorLocation, int]] = [ + valid_locations: List[ + Tuple[ + Optional[LatLngPoint], + Optional[Altitude], + Optional[OperatorAltitudeAltitudeType], + int, + ] + ] = [ ( - OperatorLocation( - position=LatLngPoint(lat=1.0, lng=1.0), - ), + LatLngPoint(lat=1.0, lng=1.0), + None, + None, 1, ), ( - OperatorLocation( - position=LatLngPoint(lat=-90.0, lng=180.0), - ), + LatLngPoint(lat=-90.0, lng=180.0), + None, + None, 1, ), ( - OperatorLocation( - position=LatLngPoint( - lat=46.2, - lng=6.1, - ), - altitude=Altitude(value=1), - altitude_type="Takeoff", + LatLngPoint( + lat=46.2, + lng=6.1, ), + Altitude(value=1), + OperatorAltitudeAltitudeType("Takeoff"), 3, ), ] for valid_location in valid_locations: _assert_operator_location(*valid_location, 0) - invalid_locations: List[Tuple[OperatorLocation, int, int]] = [ + invalid_locations: List[ + Tuple[ + Optional[LatLngPoint], + Optional[Altitude], + Optional[OperatorAltitudeAltitudeType], + int, + int, + ] + ] = [ ( - OperatorLocation( - position=LatLngPoint(lat=-90.001, lng=0), # out of range and valid - ), + LatLngPoint(lat=-90.001, lng=0), # out of range and valid + None, + None, 0, 1, ), ( - OperatorLocation( - position=LatLngPoint( - lat=0, # valid - lng=180.001, # out of range - ), + LatLngPoint( + lat=0, # valid + lng=180.001, # out of range ), + None, + None, 0, 1, ), ( - OperatorLocation( - position=LatLngPoint(lat=-90.001, lng=180.001), # both out of range - ), + LatLngPoint(lat=-90.001, lng=180.001), # both out of range + None, + None, 0, 2, ), ( - OperatorLocation( - position=LatLngPoint( - lat="46°12'7.99 N", # Float required - lng="6°08'44.48 E", # Float required - ), + LatLngPoint( + lat="46°12'7.99 N", # Float required + lng="6°08'44.48 E", # Float required ), + None, + None, 0, 2, ), ( - OperatorLocation( - position=LatLngPoint( - lat=46.2, - lng=6.1, - ), - altitude=Altitude(value=1), - altitude_type="invalid", # Invalid value + LatLngPoint( + lat=46.2, + lng=6.1, ), + Altitude(value=1), + "invalid", # Invalid value 2, 1, ), ( - OperatorLocation( - position=LatLngPoint( - lat=46.2, - lng=6.1, - ), - altitude=Altitude(value=1000.9), # Invalid value - altitude_type="Takeoff", + LatLngPoint( + lat=46.2, + lng=6.1, ), + Altitude(value=1000.9), # Invalid value + OperatorAltitudeAltitudeType("Takeoff"), 2, 1, ), ( - OperatorLocation( - position=LatLngPoint( - lat=46.2, - lng=6.1, - ), - altitude=Altitude( - value=1000.9, # Invalid value - units="FT", # Invalid value - reference="UNKNOWN", # Invalid value - ), - altitude_type="Takeoff", + LatLngPoint( + lat=46.2, + lng=6.1, + ), + Altitude( + value=1000.9, # Invalid value + units="FT", # Invalid value + reference="UNKNOWN", # Invalid value ), + "Takeoff", 2, 3, ), From d68ae2de4cbaed3b928ffff3d0e7ca73e87b646a Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Wed, 13 Sep 2023 18:42:49 +0200 Subject: [PATCH 11/37] Return from mock riddp the operational_status --- monitoring/mock_uss/riddp/routes_observation.py | 6 ++++-- monitoring/monitorlib/fetch/rid.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index 8286671791..854edd1636 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -6,6 +6,7 @@ import s2sphere from uas_standards.astm.f3411.v19.api import ErrorResponse from uas_standards.astm.f3411.v19.constants import Scope +from uas_standards.interuss.automated_testing.rid.v1.observation import OperatorAltitude from monitoring.monitorlib import geo from monitoring.monitorlib.fetch import rid as fetch @@ -54,10 +55,11 @@ def _make_flight_observation( if current_path: paths.append(current_path) + p = flight.most_recent_position current_state = observation_api.CurrentState( timestamp=p.time.isoformat(), - operational_status=None, # TODO: Propagate value + operational_status=flight.operational_status, track=None, # TODO: Propagate value speed=None # TODO: Propagate value ) @@ -183,7 +185,7 @@ def riddp_flight_details(flight_id: str) -> Tuple[str, int]: result = observation_api.GetDetailsResponse( operator=observation_api.Operator( id=details.operator_id, - location=details.operator_location.get("position"), + location=details.operator_location.position, altitude=observation_api.OperatorAltitude( altitude=details.operator_location.get("altitude"), altitude_type=details.operator_location.get("altitude_type"), diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index 05c8c61d67..2381e542fd 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -233,6 +233,17 @@ def recent_positions(self) -> List[Position]: f"Cannot retrieve recent positions using RID version {self.rid_version}" ) + @property + def operational_status(self) -> v22a.api.RIDOperationalStatus: + if self.rid_version == RIDVersion.f3411_19: + return v22a.api.RIDOperationalStatus(self.v19_value.current_state.operational_status) + elif self.rid_version == RIDVersion.f3411_22a: + return self.v22a_value.current_state.operational_status + else: + raise NotImplementedError( + f"Cannot retrieve operational status using RID version {self.rid_version}" + ) + def errors(self) -> List[str]: try: rid_version = self.rid_version From 20193f59de87f5e3552018aad9afab380265ffa0 Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Wed, 13 Sep 2023 18:47:54 +0200 Subject: [PATCH 12/37] Replace monitorlib observation_api by uas_standards --- monitoring/mock_uss/riddp/clustering.py | 2 +- .../rid_automated_testing/observation_api.py | 40 +------------------ 2 files changed, 2 insertions(+), 40 deletions(-) diff --git a/monitoring/mock_uss/riddp/clustering.py b/monitoring/mock_uss/riddp/clustering.py index e0f6ce8332..78ec8d39a2 100644 --- a/monitoring/mock_uss/riddp/clustering.py +++ b/monitoring/mock_uss/riddp/clustering.py @@ -10,7 +10,7 @@ from implicitdict import ImplicitDict from monitoring.monitorlib.rid import RIDVersion -from monitoring.monitorlib.rid_automated_testing import observation_api +from uas_standards.interuss.automated_testing.rid.v1 import observation as observation_api class Point(object): diff --git a/monitoring/monitorlib/rid_automated_testing/observation_api.py b/monitoring/monitorlib/rid_automated_testing/observation_api.py index 6ae120ac71..45086671fd 100644 --- a/monitoring/monitorlib/rid_automated_testing/observation_api.py +++ b/monitoring/monitorlib/rid_automated_testing/observation_api.py @@ -1,39 +1 @@ -from typing import List, Optional - -from implicitdict import ImplicitDict -from uas_standards.astm.f3411 import v22a - - -# Mirrors of types defined in remote ID automated testing observation API - - -class Position(ImplicitDict): - lat: float - lng: float - alt: Optional[float] - - -class Path(ImplicitDict): - positions: List[Position] - - -class Cluster(ImplicitDict): - corners: List[Position] - area_sqm: float - number_of_flights: int - - -class Flight(ImplicitDict): - id: str - most_recent_position: Optional[Position] - recent_paths: Optional[List[Path]] - - -class GetDetailsResponse(v22a.api.RIDFlightDetails): - # TODO: Update automated_testing_interface instead of using the ASTM details response schema - pass - - -class GetDisplayDataResponse(ImplicitDict): - flights: List[Flight] = [] - clusters: List[Cluster] = [] +# Replaced by uas_standards definitions From 09520f7e49f0893f07790217c3753004224c9f7b Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Wed, 13 Sep 2023 20:26:24 +0200 Subject: [PATCH 13/37] Rewording --- monitoring/monitorlib/rid_automated_testing/observation_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitoring/monitorlib/rid_automated_testing/observation_api.py b/monitoring/monitorlib/rid_automated_testing/observation_api.py index 45086671fd..7efb31144a 100644 --- a/monitoring/monitorlib/rid_automated_testing/observation_api.py +++ b/monitoring/monitorlib/rid_automated_testing/observation_api.py @@ -1 +1 @@ -# Replaced by uas_standards definitions +# Replaced by uas_standards package From b89368e406bce9dd0a3cb67ff68b5c628639db9a Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Wed, 13 Sep 2023 20:30:41 +0200 Subject: [PATCH 14/37] Format --- .../scenarios/astm/netrid/display_data_evaluator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index 47bd239863..e85feccb9e 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -421,7 +421,10 @@ def _evaluate_normal_observation( ), ) - details, query = observer.observe_flight_details(mapping.observed_flight.id) + details, query = observer.observe_flight_details( + mapping.observed_flight.id, self._rid_version + ) + logger.info(f"Received flight {details} - {query}") self._test_scenario.record_query(query) self._common_dictionary_evaluator.evaluate_dp_details( From e956133ebd7d4856376463dd0d947eb9be11bf35 Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Wed, 13 Sep 2023 22:23:47 +0200 Subject: [PATCH 15/37] Add check for astm.f3411.v22a.NET0470,Table1,5 --- .../mock_uss/riddp/routes_observation.py | 7 +++-- .../resources/netrid/observers.py | 4 ++- .../netrid/common_dictionary_evaluator.py | 26 +++++++++++++++++++ .../common_dictionary_evaluator_test.py | 22 +++++++++++++++- .../astm/netrid/display_data_evaluator.py | 4 +++ .../astm/netrid/v22a/nominal_behavior.md | 4 +++ 6 files changed, 61 insertions(+), 6 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index 854edd1636..012ad614d1 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -1,12 +1,10 @@ from typing import Dict, List, Optional, Tuple -from datetime import datetime import arrow import flask from loguru import logger import s2sphere from uas_standards.astm.f3411.v19.api import ErrorResponse from uas_standards.astm.f3411.v19.constants import Scope -from uas_standards.interuss.automated_testing.rid.v1.observation import OperatorAltitude from monitoring.monitorlib import geo from monitoring.monitorlib.fetch import rid as fetch @@ -55,10 +53,11 @@ def _make_flight_observation( if current_path: paths.append(current_path) - p = flight.most_recent_position + MICRO=pow(10, 6) + t = p.time.replace(microsecond=int(int(p.time.microsecond/MICRO*10)*MICRO/10)) current_state = observation_api.CurrentState( - timestamp=p.time.isoformat(), + timestamp=t.isoformat(), operational_status=flight.operational_status, track=None, # TODO: Propagate value speed=None # TODO: Propagate value diff --git a/monitoring/uss_qualifier/resources/netrid/observers.py b/monitoring/uss_qualifier/resources/netrid/observers.py index 869587301b..2448df2c79 100644 --- a/monitoring/uss_qualifier/resources/netrid/observers.py +++ b/monitoring/uss_qualifier/resources/netrid/observers.py @@ -8,7 +8,9 @@ from monitoring.monitorlib.fetch import QueryType from monitoring.monitorlib.infrastructure import UTMClientSession from monitoring.monitorlib.rid import RIDVersion -from uas_standards.interuss.automated_testing.rid.v1 import observation as observation_api +from uas_standards.interuss.automated_testing.rid.v1 import ( + observation as observation_api, +) from monitoring.uss_qualifier.resources.resource import Resource from monitoring.uss_qualifier.resources.communications import AuthAdapterResource diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index cdfcb68a8b..d1bf0010f8 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -1,4 +1,6 @@ import datetime +from arrow import ParserError +from implicitdict import StringBasedDateTime from typing import List, Optional import s2sphere @@ -268,6 +270,30 @@ def _evaluate_plain_uas_id(self, value: str, participants: List[str]): # TODO: Add specific session id format check # TODO: Add a check to validate at least one format is correct + def evaluate_timestamp(self, timestamp: str, participants: List[str]): + with self._test_scenario.check( + "Timestamp consistency with Common Dictionary", participants + ) as check: + try: + t = StringBasedDateTime(timestamp) + if t.datetime.utcoffset().seconds != 0: + check.record_failed( + f"Timestamp must be relative to UTC: {timestamp}", + severity=Severity.Medium, + ) + deci_seconds = t.datetime.microsecond / 100000 + if deci_seconds - int(deci_seconds) > 0: + check.record_failed( + f"Timestamp resolution is smaller than 1/10: {timestamp}", + severity=Severity.Medium, + ) + except ParserError as e: + check.record_failed( + f"Unable to parse timestamp: {timestamp}", + details=f"Reason: {e}", + severity=Severity.Medium, + ) + def _evaluate_operator_id(self, value: Optional[str], participants: List[str]): if self._rid_version == RIDVersion.f3411_22a: if value: diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py index 7c34c6a964..109b60f0fe 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py @@ -33,7 +33,6 @@ def step_under_test(self: UnitTestScenario): unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome - def test_operator_id_non_ascii(): _assert_operator_id("non_ascii©", False) @@ -200,6 +199,27 @@ def test_operational_status(): _assert_operational_status("Invalid", False) # Invalid +def _assert_timestamp(value: str, outcome: bool): + def step_under_test(self: UnitTestScenario): + evaluator = RIDCommonDictionaryEvaluator( + config=EvaluationConfiguration(), + test_scenario=self, + rid_version=RIDVersion.f3411_22a, + ) + + evaluator.evaluate_timestamp(value, []) + + unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() + assert unit_test_scenario.get_report().successful == outcome + + +def test_timestamp(): + _assert_timestamp("2023-09-13T04:43:00.1Z", True) # Ok + _assert_timestamp("2023-09-13T04:43:00Z", True) # Ok + _assert_timestamp("2023-09-13T04:43:00.501Z", False) # Wrong resolution + _assert_timestamp("2023-09-13T04:43:00.1EST", False) # Wrong timezone + + def _assert_evaluate_sp_flight_recent_positions( f: Flight, query_time: datetime, outcome: bool ): diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index e85feccb9e..c8778f2761 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -320,6 +320,10 @@ def _evaluate_observation( verified_sps, ) + if observation: + flights: List[Flight] = observation.get("flights", []) + [self._common_dictionary_evaluator.evaluate_timestamp(f.current_state.timestamp, participants=[observer.participant_id]) for f in flights] + def _evaluate_normal_observation( self, observer: RIDSystemObserver, diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md index 8d61b8db1f..272398cf0c 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md @@ -200,6 +200,10 @@ This check validates that the display area of a cluster, measured and provided i **[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the UAS ID is in serial number format. (**[astm.f3411.v22a.NET0470,Table1,1a](../../../../requirements/astm/f3411/v22a.md)**) +#### Timestamp consistency with Common Dictionary check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that timestamps are expressed with a minimum resolution of one tenth of a second and relative to UTC. (**[astm.f3411.v22a.NET0470,Table1,5](../../../../requirements/astm/f3411/v22a.md)**) + #### Operational Status consistency with Common Dictionary check **[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Operational Status, if present, is valid. (**[astm.f3411.v22a.NET0470,Table1,7](../../../../requirements/astm/f3411/v22a.md)**) From 2f54ba1bd485a92abf5e1ec11cae45b0e4b83133 Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Thu, 14 Sep 2023 00:40:56 +0200 Subject: [PATCH 16/37] Add check for astm.f3411.v22a.NET0470,Table1,20 --- .../mock_uss/riddp/routes_observation.py | 31 ++++++++++++++++--- monitoring/monitorlib/formatting.py | 5 +++ .../netrid/common_dictionary_evaluator.py | 25 +++++++++++++-- .../astm/netrid/display_data_evaluator.py | 3 +- .../astm/netrid/v22a/nominal_behavior.md | 5 +++ 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index 012ad614d1..e3dcaf8027 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -1,3 +1,4 @@ +from s2sphere import LatLng from typing import Dict, List, Optional, Tuple import arrow import flask @@ -6,9 +7,11 @@ from uas_standards.astm.f3411.v19.api import ErrorResponse from uas_standards.astm.f3411.v19.constants import Scope +from uas_standards.astm.f3411.v22a.constants import MaxSpeed, SpecialSpeed, MinSpeedResolution + from monitoring.monitorlib import geo from monitoring.monitorlib.fetch import rid as fetch -from monitoring.monitorlib.fetch.rid import Flight, FetchedISAs +from monitoring.monitorlib.fetch.rid import Flight, FetchedISAs, Position from monitoring.monitorlib.rid import RIDVersion from uas_standards.interuss.automated_testing.rid.v1 import observation as observation_api from monitoring.mock_uss import webapp @@ -17,7 +20,24 @@ from .behavior import DisplayProviderBehavior from .config import KEY_RID_VERSION from .database import db - +from ...monitorlib.formatting import _limit_resolution +from ...monitorlib.geo import EARTH_CIRCUMFERENCE_M + + +def _compute_speed(positions: List[Position]): + if len(positions) == 2: + p0 = positions[0] + p1 = positions[1] + if p0.time < p1.time: + p0 = positions[1] + p1 = positions[0] + c0 = LatLng.from_degrees(p0.lat, p0.lng) + c1 = LatLng.from_degrees(p1.lat, p1.lng) + distance_m = c0.get_distance(c1).degrees * EARTH_CIRCUMFERENCE_M / 360 + duration_s = (p1.time - p0.time).seconds + return max(_limit_resolution(distance_m / duration_s, MinSpeedResolution), MaxSpeed) + else: + return SpecialSpeed def _make_flight_observation( flight: Flight, view: s2sphere.LatLngRect @@ -53,14 +73,15 @@ def _make_flight_observation( if current_path: paths.append(current_path) + current_speed = _compute_speed(flight.recent_positions[:2]) + p = flight.most_recent_position - MICRO=pow(10, 6) - t = p.time.replace(microsecond=int(int(p.time.microsecond/MICRO*10)*MICRO/10)) + t = p.time.replace(microsecond=_limit_resolution(p.time.microsecond, pow(10, 5))) current_state = observation_api.CurrentState( timestamp=t.isoformat(), operational_status=flight.operational_status, track=None, # TODO: Propagate value - speed=None # TODO: Propagate value + speed=current_speed ) return observation_api.Flight( id=flight.id, diff --git a/monitoring/monitorlib/formatting.py b/monitoring/monitorlib/formatting.py index e428281840..6436a00e2e 100644 --- a/monitoring/monitorlib/formatting.py +++ b/monitoring/monitorlib/formatting.py @@ -141,3 +141,8 @@ def make_datetime(t) -> datetime.datetime: return arrow.get(t).datetime else: raise ValueError("Could not convert {} to datetime".format(str(type(t)))) + + +def _limit_resolution(value: float, resolution: float): + """Change resolution of a value""" + return round(value / resolution) * resolution diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index d1bf0010f8..b79023463d 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -1,6 +1,7 @@ import datetime from arrow import ParserError from implicitdict import StringBasedDateTime +from loguru import logger from typing import List, Optional import s2sphere @@ -11,10 +12,13 @@ from uas_standards.ansi_cta_2063_a import SerialNumber from uas_standards.astm.f3411 import v22a + +from uas_standards.astm.f3411.v22a.constants import SpecialSpeed, MaxSpeed from monitoring.monitorlib.fetch.rid import ( FetchedFlights, FlightDetails, ) +from monitoring.monitorlib.formatting import _limit_resolution from monitoring.monitorlib.geo import validate_lat, validate_lng, Altitude, LatLngPoint from monitoring.monitorlib.rid import RIDVersion from monitoring.uss_qualifier.common_data_definitions import Severity @@ -281,8 +285,9 @@ def evaluate_timestamp(self, timestamp: str, participants: List[str]): f"Timestamp must be relative to UTC: {timestamp}", severity=Severity.Medium, ) - deci_seconds = t.datetime.microsecond / 100000 - if deci_seconds - int(deci_seconds) > 0: + ms = t.datetime.microsecond + ms_res = _limit_resolution(ms, pow(10, 5)) + if ms != ms_res: check.record_failed( f"Timestamp resolution is smaller than 1/10: {timestamp}", severity=Severity.Medium, @@ -312,6 +317,20 @@ def _evaluate_operator_id(self, value: Optional[str], participants: List[str]): message=f"Unsupported version {self._rid_version}: skipping Operator ID evaluation", ) + def evaluate_speed(self, speed: float, participants: List[str]): + with self._test_scenario.check( + "Speed consistency with Common Dictionary", participants + ) as check: + if not (0 < speed <= MaxSpeed or round(speed) == SpecialSpeed): + check.record_failed( + f"Invalid speed: {speed}", + details=f"The speed shall be greater than 0 and less than {MaxSpeed}. The Special Value {SpecialSpeed} is allowed.", + severity=Severity.Medium, + ) + + # TODO: In addition, if the value resolution is less than 0.25 m/s, this check will fail. + + def _evaluate_operator_location( self, position: Optional[LatLngPoint], @@ -368,7 +387,7 @@ def _evaluate_operator_location( details=f"Invalid Operator Altitude units: {alt.units}", severity=Severity.Medium, ) - if alt.value != round(alt.value): + if alt.value != _limit_resolution(alt.value, 1): check.record_failed( "Operator Altitude must have a minimum resolution of 1 m.", details=f"Invalid Operator Altitude: {alt.value}", diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index c8778f2761..81cf286851 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -386,6 +386,8 @@ def _evaluate_normal_observation( Severity.Medium, details=f"{mapping.injected_flight.uss_participant_id}'s flight with injection ID {mapping.injected_flight.flight.injection_id} in test {mapping.injected_flight.test_id} had telemetry index {mapping.telemetry_index} at {injected_telemetry.timestamp} with lat={injected_telemetry.position.lat}, lng={injected_telemetry.position.lng}, alt={injected_telemetry.position.alt}, but {observer.participant_id} observed lat={observed_position.lat}, lng={observed_position.lng}, alt={observed_position.alt} at {query.request.initiated_at}", ) + observed_speed = mapping.observed_flight.current_state.speed + self._common_dictionary_evaluator.evaluate_speed(observed_speed, [observer.participant_id]) # Check that flights using telemetry are not using extrapolated position data for mapping in mapping_by_injection_id.values(): @@ -428,7 +430,6 @@ def _evaluate_normal_observation( details, query = observer.observe_flight_details( mapping.observed_flight.id, self._rid_version ) - logger.info(f"Received flight {details} - {query}") self._test_scenario.record_query(query) self._common_dictionary_evaluator.evaluate_dp_details( diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md index 272398cf0c..0cb03beb06 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md @@ -212,6 +212,11 @@ This check validates that the display area of a cluster, measured and provided i **[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall (NET0470) provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Operator ID, if present, is valid. (**[astm.f3411.v22a.NET0470,Table1,9](../../../../requirements/astm/f3411/v22a.md)**) +#### Speed consistency with Common Dictionary check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Speed (**[astm.f3411.v22a.NET0470,Table1,20](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the transmitted Speed is greater than 254.25 and different than 255, this check will fail. + + #### Operator Location consistency with Common Dictionary check **[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Operator Latitude (**[astm.f3411.v22a.NET0470,Table1,23](../../../../requirements/astm/f3411/v22a.md)**) and Longitude (**[astm.f3411.v22a.NET0470,Table1,24](../../../../requirements/astm/f3411/v22a.md)**), if present, are valid. From a0c165936d00cff91dcdf02cfb4f49ca26b624ba Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Thu, 14 Sep 2023 00:50:30 +0200 Subject: [PATCH 17/37] Add check for astm.f3411.v22a.NET0470,Table1,20 resolution --- .../netrid/common_dictionary_evaluator.py | 13 ++++++++--- .../common_dictionary_evaluator_test.py | 23 +++++++++++++++++++ .../astm/netrid/v22a/nominal_behavior.md | 3 +-- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index b79023463d..aa46d4aeaa 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -1,7 +1,6 @@ import datetime from arrow import ParserError from implicitdict import StringBasedDateTime -from loguru import logger from typing import List, Optional import s2sphere @@ -13,7 +12,8 @@ from uas_standards.ansi_cta_2063_a import SerialNumber from uas_standards.astm.f3411 import v22a -from uas_standards.astm.f3411.v22a.constants import SpecialSpeed, MaxSpeed +from uas_standards.astm.f3411.v22a.constants import SpecialSpeed, MaxSpeed, MinSpeedResolution + from monitoring.monitorlib.fetch.rid import ( FetchedFlights, FlightDetails, @@ -328,7 +328,14 @@ def evaluate_speed(self, speed: float, participants: List[str]): severity=Severity.Medium, ) - # TODO: In addition, if the value resolution is less than 0.25 m/s, this check will fail. + if speed != _limit_resolution(speed, MinSpeedResolution): + check.record_failed( + f"Invalid speed resolution: {speed}", + details=f"the speed resolution shall not be less than 0.25 m/s", + severity=Severity.Medium, + ) + + def _evaluate_operator_location( diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py index 109b60f0fe..e4929fa550 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py @@ -213,6 +213,27 @@ def step_under_test(self: UnitTestScenario): assert unit_test_scenario.get_report().successful == outcome +def test_speed(): + _assert_speed(1, True) # Ok + _assert_speed(20.75, True) # Ok + _assert_speed(400, False) # Fail, above MaxSpeed + _assert_speed(23.3, False) # Wrong resolution + + +def _assert_speed(value: float, outcome: bool): + def step_under_test(self: UnitTestScenario): + evaluator = RIDCommonDictionaryEvaluator( + config=EvaluationConfiguration(), + test_scenario=self, + rid_version=RIDVersion.f3411_22a, + ) + + evaluator.evaluate_speed(value, []) + + unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() + assert unit_test_scenario.get_report().successful == outcome + + def test_timestamp(): _assert_timestamp("2023-09-13T04:43:00.1Z", True) # Ok _assert_timestamp("2023-09-13T04:43:00Z", True) # Ok @@ -220,6 +241,8 @@ def test_timestamp(): _assert_timestamp("2023-09-13T04:43:00.1EST", False) # Wrong timezone + + def _assert_evaluate_sp_flight_recent_positions( f: Flight, query_time: datetime, outcome: bool ): diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md index 0cb03beb06..f8be4b919b 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md @@ -214,8 +214,7 @@ This check validates that the display area of a cluster, measured and provided i #### Speed consistency with Common Dictionary check -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Speed (**[astm.f3411.v22a.NET0470,Table1,20](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the transmitted Speed is greater than 254.25 and different than 255, this check will fail. - +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Speed (**[astm.f3411.v22a.NET0470,Table1,20](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the transmitted Speed is greater than 254.25 and different than 255, this check will fail. In addition, if the speed resolution is less than 0.25 m/s, this check will fail. #### Operator Location consistency with Common Dictionary check From 7989eb943b5f93873e3b42ee7dc93f5ea276153f Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Thu, 14 Sep 2023 11:27:26 +0200 Subject: [PATCH 18/37] Add check for astm.f3411.v22a.NET0470,Table1,19 track and use injected speed --- .../mock_uss/riddp/routes_observation.py | 36 ++++++------------- monitoring/monitorlib/fetch/rid.py | 23 ++++++++++++ .../netrid/common_dictionary_evaluator.py | 19 ++++++++-- .../astm/netrid/v22a/nominal_behavior.md | 6 +++- 4 files changed, 55 insertions(+), 29 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index e3dcaf8027..e33368d28f 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -6,9 +6,10 @@ import s2sphere from uas_standards.astm.f3411.v19.api import ErrorResponse from uas_standards.astm.f3411.v19.constants import Scope +from uas_standards.astm.f3411.v22a.api import RIDHeight +from uas_standards.astm.f3411.v22a.constants import MaxSpeed -from uas_standards.astm.f3411.v22a.constants import MaxSpeed, SpecialSpeed, MinSpeedResolution - +from uas_standards.astm.f3411.v22a.constants import MinHeightResolution from monitoring.monitorlib import geo from monitoring.monitorlib.fetch import rid as fetch from monitoring.monitorlib.fetch.rid import Flight, FetchedISAs, Position @@ -21,23 +22,6 @@ from .config import KEY_RID_VERSION from .database import db from ...monitorlib.formatting import _limit_resolution -from ...monitorlib.geo import EARTH_CIRCUMFERENCE_M - - -def _compute_speed(positions: List[Position]): - if len(positions) == 2: - p0 = positions[0] - p1 = positions[1] - if p0.time < p1.time: - p0 = positions[1] - p1 = positions[0] - c0 = LatLng.from_degrees(p0.lat, p0.lng) - c1 = LatLng.from_degrees(p1.lat, p1.lng) - distance_m = c0.get_distance(c1).degrees * EARTH_CIRCUMFERENCE_M / 360 - duration_s = (p1.time - p0.time).seconds - return max(_limit_resolution(distance_m / duration_s, MinSpeedResolution), MaxSpeed) - else: - return SpecialSpeed def _make_flight_observation( flight: Flight, view: s2sphere.LatLngRect @@ -73,19 +57,19 @@ def _make_flight_observation( if current_path: paths.append(current_path) - current_speed = _compute_speed(flight.recent_positions[:2]) - p = flight.most_recent_position - t = p.time.replace(microsecond=_limit_resolution(p.time.microsecond, pow(10, 5))) + original_time = p.time.replace(microsecond=_limit_resolution(p.time.microsecond, pow(10, 5))) current_state = observation_api.CurrentState( - timestamp=t.isoformat(), + timestamp=original_time.isoformat(), operational_status=flight.operational_status, - track=None, # TODO: Propagate value - speed=current_speed + track=flight.track, + speed=_limit_resolution(flight.speed, MaxSpeed) ) + h = p.height if "height" in p else RIDHeight(distance=-1000) + h.distance = _limit_resolution(h.distance, MinHeightResolution) return observation_api.Flight( id=flight.id, - most_recent_position=observation_api.Position(lat=p.lat, lng=p.lng, alt=p.alt), + most_recent_position=observation_api.Position(lat=p.lat, lng=p.lng, alt=p.alt, height=h), recent_paths=[observation_api.Path(positions=path) for path in paths], current_state=current_state ) diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index 2381e542fd..a044e938d5 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -10,6 +10,7 @@ import uas_standards.astm.f3411.v22a.api import uas_standards.astm.f3411.v22a.constants import yaml +from uas_standards.astm.f3411.v22a.api import RIDHeight from yaml.representer import Representer from monitoring.monitorlib import fetch, rid_v1, rid_v2, geo @@ -244,6 +245,28 @@ def operational_status(self) -> v22a.api.RIDOperationalStatus: f"Cannot retrieve operational status using RID version {self.rid_version}" ) + @property + def track(self): + if self.rid_version == RIDVersion.f3411_19: + return self.v19_value.current_state.track + elif self.rid_version == RIDVersion.f3411_22a: + return self.v22a_value.current_state.track + else: + raise NotImplementedError( + f"Cannot retrieve track using RID version {self.rid_version}" + ) + + @property + def speed(self): + if self.rid_version == RIDVersion.f3411_19: + return self.v19_value.current_state.speed + elif self.rid_version == RIDVersion.f3411_22a: + return self.v22a_value.current_state.speed + else: + raise NotImplementedError( + f"Cannot retrieve track using RID version {self.rid_version}" + ) + def errors(self) -> List[str]: try: rid_version = self.rid_version diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index aa46d4aeaa..337bea5e19 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -12,7 +12,7 @@ from uas_standards.ansi_cta_2063_a import SerialNumber from uas_standards.astm.f3411 import v22a -from uas_standards.astm.f3411.v22a.constants import SpecialSpeed, MaxSpeed, MinSpeedResolution +from uas_standards.astm.f3411.v22a.constants import SpecialSpeed, MaxSpeed, MinSpeedResolution, SpecialTrackDirection, MinTrackDirection, MaxTrackDirection, MinTrackDirectionResolution from monitoring.monitorlib.fetch.rid import ( FetchedFlights, @@ -335,8 +335,23 @@ def evaluate_speed(self, speed: float, participants: List[str]): severity=Severity.Medium, ) + def evaluate_track(self, track: float, participants: List[str]): + with self._test_scenario.check( + "Track Direction consistency with Common Dictionary", participants + ) as check: + if not (MinTrackDirection <= track <= MaxTrackDirection or round(track) == SpecialTrackDirection): + check.record_failed( + f"Invalid track direction: {track}", + details=f"The track direction shall be greater than -360 and less than {MaxSpeed}. The Special Value {SpecialSpeed} is allowed.", + severity=Severity.Medium, + ) - + if track != _limit_resolution(track, MinTrackDirectionResolution): + check.record_failed( + f"Invalid track direction resolution: {track}", + details=f"the track direction resolution shall not be less than 1 degree", + severity=Severity.Medium, + ) def _evaluate_operator_location( self, diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md index f8be4b919b..3c7473f3ff 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md @@ -212,9 +212,13 @@ This check validates that the display area of a cluster, measured and provided i **[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall (NET0470) provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Operator ID, if present, is valid. (**[astm.f3411.v22a.NET0470,Table1,9](../../../../requirements/astm/f3411/v22a.md)**) +#### Track Direction consistency with Common Dictionary check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Track Direction (**[astm.f3411.v22a.NET0470,Table1,19](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Track Direction is less than -359 and is greater than 359 and different than 361 this check will fail. If the Track Direction resolution is less than 1 degree, this check will fail. + #### Speed consistency with Common Dictionary check -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Speed (**[astm.f3411.v22a.NET0470,Table1,20](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the transmitted Speed is greater than 254.25 and different than 255, this check will fail. In addition, if the speed resolution is less than 0.25 m/s, this check will fail. +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Speed (**[astm.f3411.v22a.NET0470,Table1,20](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Speed is greater than 254.25 and different than 255, this check will fail. If the Speed resolution is less than 0.25 m/s, this check will fail. #### Operator Location consistency with Common Dictionary check From 531fb039fda050e8e11b492e0091714555ca928f Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Thu, 14 Sep 2023 13:22:56 +0200 Subject: [PATCH 19/37] Add check for astm.f3411.v22a.NET0470,Table1,14-15 height --- .../netrid/common_dictionary_evaluator.py | 24 +++++++- .../common_dictionary_evaluator_test.py | 55 +++++++++++++++---- .../astm/netrid/v22a/nominal_behavior.md | 8 +++ 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index 337bea5e19..8315267adf 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -6,7 +6,7 @@ from uas_standards.interuss.automated_testing.rid.v1.observation import ( GetDetailsResponse, - OperatorAltitudeAltitudeType, + OperatorAltitudeAltitudeType, RIDHeight, RIDHeightReference, ) from uas_standards.ansi_cta_2063_a import SerialNumber @@ -14,6 +14,7 @@ from uas_standards.astm.f3411.v22a.constants import SpecialSpeed, MaxSpeed, MinSpeedResolution, SpecialTrackDirection, MinTrackDirection, MaxTrackDirection, MinTrackDirectionResolution +from interfaces.uas_standards.src.uas_standards.astm.f3411.v22a.constants import MinHeightResolution from monitoring.monitorlib.fetch.rid import ( FetchedFlights, FlightDetails, @@ -353,6 +354,27 @@ def evaluate_track(self, track: float, participants: List[str]): severity=Severity.Medium, ) + def evaluate_height(self, height: Optional[RIDHeight], participants: List[str]): + if height: + with self._test_scenario.check( + "Height consistency with Common Dictionary", participants + ) as check: + if height.distance != _limit_resolution(height.distance, MinHeightResolution): + check.record_failed( + f"Invalid height resolution: {height.distance}", + details=f"the height resolution shall not be less than 1 meter", + severity=Severity.Medium, + ) + with self._test_scenario.check( + "Height Type consistency with Common Dictionary", participants + ) as check: + if height.reference != RIDHeightReference.TakeoffLocation and height.reference != RIDHeightReference.GroundLevel: + check.record_failed( + f"Invalid height type: {height.reference}", + details=f"The height type reference shall be either {RIDHeightReference.TakeoffLocation} or {RIDHeightReference.GroundLevel}", + severity=Severity.Medium, + ) + def _evaluate_operator_location( self, position: Optional[LatLngPoint], diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py index e4929fa550..6e8dc13221 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py @@ -2,9 +2,10 @@ import s2sphere from typing import List, Tuple, Optional from uas_standards.interuss.automated_testing.rid.v1.observation import ( - OperatorAltitudeAltitudeType, + OperatorAltitudeAltitudeType, RIDHeight, RIDHeightReference, ) +from uas_standards.astm.f3411.v22a.constants import SpecialTrackDirection from monitoring.monitorlib.rid import RIDVersion from monitoring.monitorlib.fetch.rid import Flight from monitoring.uss_qualifier.scenarios.astm.netrid.common_dictionary_evaluator import ( @@ -15,7 +16,6 @@ from uas_standards.astm.f3411.v22a.api import ( Altitude, LatLngPoint, - OperatorLocation, UAType, ) from uas_standards.astm.f3411 import v22a @@ -212,6 +212,24 @@ def step_under_test(self: UnitTestScenario): unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome +def test_timestamp(): + _assert_timestamp("2023-09-13T04:43:00.1Z", True) # Ok + _assert_timestamp("2023-09-13T04:43:00Z", True) # Ok + _assert_timestamp("2023-09-13T04:43:00.501Z", False) # Wrong resolution + _assert_timestamp("2023-09-13T04:43:00.1EST", False) # Wrong timezone + +def _assert_speed(value: float, outcome: bool): + def step_under_test(self: UnitTestScenario): + evaluator = RIDCommonDictionaryEvaluator( + config=EvaluationConfiguration(), + test_scenario=self, + rid_version=RIDVersion.f3411_22a, + ) + + evaluator.evaluate_speed(value, []) + + unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() + assert unit_test_scenario.get_report().successful == outcome def test_speed(): _assert_speed(1, True) # Ok @@ -219,8 +237,7 @@ def test_speed(): _assert_speed(400, False) # Fail, above MaxSpeed _assert_speed(23.3, False) # Wrong resolution - -def _assert_speed(value: float, outcome: bool): +def _assert_track(value: float, outcome: bool): def step_under_test(self: UnitTestScenario): evaluator = RIDCommonDictionaryEvaluator( config=EvaluationConfiguration(), @@ -228,20 +245,38 @@ def step_under_test(self: UnitTestScenario): rid_version=RIDVersion.f3411_22a, ) - evaluator.evaluate_speed(value, []) + evaluator.evaluate_track(value, []) unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome +def test_track(): + _assert_track(1, True) # Ok + _assert_track(-359, True) # Ok + _assert_track(400, False) # Fail, above MaxTrackDirection + _assert_track(-360, False) # Fail, below MinTrackDirection + _assert_track(23.3, False) # Wrong resolution + _assert_track(SpecialTrackDirection, True) -def test_timestamp(): - _assert_timestamp("2023-09-13T04:43:00.1Z", True) # Ok - _assert_timestamp("2023-09-13T04:43:00Z", True) # Ok - _assert_timestamp("2023-09-13T04:43:00.501Z", False) # Wrong resolution - _assert_timestamp("2023-09-13T04:43:00.1EST", False) # Wrong timezone +def _assert_height(value: RIDHeight, outcome: bool): + def step_under_test(self: UnitTestScenario): + evaluator = RIDCommonDictionaryEvaluator( + config=EvaluationConfiguration(), + test_scenario=self, + rid_version=RIDVersion.f3411_22a, + ) + + evaluator.evaluate_height(value, []) + unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() + assert unit_test_scenario.get_report().successful == outcome +def test_height(): + _assert_height(None, True) # Ok + _assert_height(RIDHeight(distance=10, reference="TakeoffLocation"), True) # Ok + _assert_height(RIDHeight(distance=10.101, reference="TakeoffLocation"), False) # Wrong resolution + _assert_height(RIDHeight(distance=10.101, reference="Moon"), False) # Wrong reference def _assert_evaluate_sp_flight_recent_positions( f: Flight, query_time: datetime, outcome: bool diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md index 3c7473f3ff..a29450d322 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md @@ -212,6 +212,14 @@ This check validates that the display area of a cluster, measured and provided i **[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall (NET0470) provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Operator ID, if present, is valid. (**[astm.f3411.v22a.NET0470,Table1,9](../../../../requirements/astm/f3411/v22a.md)**) +#### Height consistency with Common Dictionary check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Track Direction (**[astm.f3411.v22a.NET0470,Table1,14](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Height resolution is less than 1 meter, this check will fail. + +#### Height Type consistency with Common Dictionary check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Height Type (**[astm.f3411.v22a.NET0470,Table1,15](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Height Type indicates a value different than Takeoff Location or Ground Level, this check will fail. + #### Track Direction consistency with Common Dictionary check **[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Track Direction (**[astm.f3411.v22a.NET0470,Table1,19](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Track Direction is less than -359 and is greater than 359 and different than 361 this check will fail. If the Track Direction resolution is less than 1 degree, this check will fail. From 53be4af1bcff877cbad7ea92c3ac300cf996843a Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Thu, 14 Sep 2023 15:35:46 +0200 Subject: [PATCH 20/37] Add check for astm.f3411.v22a.NET0470,Table1,10-11 current position --- .../mock_uss/riddp/routes_observation.py | 13 ++-- monitoring/monitorlib/fetch/rid.py | 6 +- .../netrid/common_dictionary_evaluator.py | 61 ++++++++++++++----- .../common_dictionary_evaluator_test.py | 8 +-- .../astm/netrid/display_data_evaluator.py | 7 +-- .../astm/netrid/v22a/nominal_behavior.md | 7 ++- 6 files changed, 67 insertions(+), 35 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index e33368d28f..c47c8a3471 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -1,4 +1,3 @@ -from s2sphere import LatLng from typing import Dict, List, Optional, Tuple import arrow import flask @@ -6,10 +5,7 @@ import s2sphere from uas_standards.astm.f3411.v19.api import ErrorResponse from uas_standards.astm.f3411.v19.constants import Scope -from uas_standards.astm.f3411.v22a.api import RIDHeight -from uas_standards.astm.f3411.v22a.constants import MaxSpeed - -from uas_standards.astm.f3411.v22a.constants import MinHeightResolution +from uas_standards.astm.f3411.v22a.constants import MaxSpeed, MinHeightResolution, MinTrackDirectionResolution from monitoring.monitorlib import geo from monitoring.monitorlib.fetch import rid as fetch from monitoring.monitorlib.fetch.rid import Flight, FetchedISAs, Position @@ -62,11 +58,12 @@ def _make_flight_observation( current_state = observation_api.CurrentState( timestamp=original_time.isoformat(), operational_status=flight.operational_status, - track=flight.track, + track=_limit_resolution(flight.track, MinTrackDirectionResolution), speed=_limit_resolution(flight.speed, MaxSpeed) ) - h = p.height if "height" in p else RIDHeight(distance=-1000) - h.distance = _limit_resolution(h.distance, MinHeightResolution) + h = p.get("height") + if h: + h.distance = _limit_resolution(h.distance, MinHeightResolution) return observation_api.Flight( id=flight.id, most_recent_position=observation_api.Position(lat=p.lat, lng=p.lng, alt=p.alt, height=h), diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index a044e938d5..3af2760391 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -150,17 +150,19 @@ class Position(ImplicitDict): time: datetime.datetime """Timestamp for the position.""" + height: Optional[RIDHeight] + @staticmethod def from_v19_rid_aircraft_position( p: v19.api.RIDAircraftPosition, t: v19.api.StringBasedDateTime ) -> Position: - return Position(lat=p.lat, lng=p.lng, alt=p.alt, time=t.datetime) + return Position(lat=p.lat, lng=p.lng, alt=p.alt, time=t.datetime, height=None) @staticmethod def from_v22a_rid_aircraft_position( p: v22a.api.RIDAircraftPosition, t: v22a.api.StringBasedDateTime ) -> Position: - return Position(lat=p.lat, lng=p.lng, alt=p.alt, time=t.datetime) + return Position(lat=p.lat, lng=p.lng, alt=p.alt, time=t.datetime, height=p.get("height")) class Flight(ImplicitDict): diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index 8315267adf..c11a876015 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -14,7 +14,8 @@ from uas_standards.astm.f3411.v22a.constants import SpecialSpeed, MaxSpeed, MinSpeedResolution, SpecialTrackDirection, MinTrackDirection, MaxTrackDirection, MinTrackDirectionResolution -from interfaces.uas_standards.src.uas_standards.astm.f3411.v22a.constants import MinHeightResolution +from interfaces.uas_standards.src.uas_standards.astm.f3411.v22a.constants import MinHeightResolution, \ + MinPositionResolution from monitoring.monitorlib.fetch.rid import ( FetchedFlights, FlightDetails, @@ -67,19 +68,26 @@ def evaluate_sp_flights( requested_area, f, participants ) - if self._rid_version == RIDVersion.f3411_22a: - for f in observed_flights.flights: - # Evaluate on all flights regardless of where they came from - self._evaluate_operational_status( - f.v22a_value.get("current_state", {}).get("operational_status"), - participants, - ) for f in observed_flights.flights: + # Evaluate on all flights regardless of where they came from self._evaluate_operational_status( - f.raw.get("current_state", {}).get("operational_status"), + f.operational_status, participants, ) + def evaluate_dp_flight( + self, + observed_flight: Flight, + participants: List[str], + ): + current_state = observed_flight.current_state + self._evaluate_speed(current_state.speed, participants) + self._evaluate_track(current_state.track, participants) + self._evaluate_timestamp(current_state.timestamp, participants) + self._evaluate_operational_status(current_state.operational_status, participants) + self._evaluate_position(observed_flight.most_recent_position, participants) + self._evaluate_height(observed_flight.most_recent_position.get("height"), participants) + def _evaluate_recent_position_time( self, p: Position, query_time: datetime.datetime, check: PendingCheck ): @@ -275,7 +283,7 @@ def _evaluate_plain_uas_id(self, value: str, participants: List[str]): # TODO: Add specific session id format check # TODO: Add a check to validate at least one format is correct - def evaluate_timestamp(self, timestamp: str, participants: List[str]): + def _evaluate_timestamp(self, timestamp: str, participants: List[str]): with self._test_scenario.check( "Timestamp consistency with Common Dictionary", participants ) as check: @@ -318,11 +326,11 @@ def _evaluate_operator_id(self, value: Optional[str], participants: List[str]): message=f"Unsupported version {self._rid_version}: skipping Operator ID evaluation", ) - def evaluate_speed(self, speed: float, participants: List[str]): + def _evaluate_speed(self, speed: float, participants: List[str]): with self._test_scenario.check( "Speed consistency with Common Dictionary", participants ) as check: - if not (0 < speed <= MaxSpeed or round(speed) == SpecialSpeed): + if not (0 <= speed <= MaxSpeed or round(speed) == SpecialSpeed): check.record_failed( f"Invalid speed: {speed}", details=f"The speed shall be greater than 0 and less than {MaxSpeed}. The Special Value {SpecialSpeed} is allowed.", @@ -336,7 +344,7 @@ def evaluate_speed(self, speed: float, participants: List[str]): severity=Severity.Medium, ) - def evaluate_track(self, track: float, participants: List[str]): + def _evaluate_track(self, track: float, participants: List[str]): with self._test_scenario.check( "Track Direction consistency with Common Dictionary", participants ) as check: @@ -354,7 +362,32 @@ def evaluate_track(self, track: float, participants: List[str]): severity=Severity.Medium, ) - def evaluate_height(self, height: Optional[RIDHeight], participants: List[str]): + def _evaluate_position( + self, position: Position, participants: List[str] + ): + with self._test_scenario.check( + "Current Position consistency with Common Dictionary", participants + ) as check: + lat = position.lat + try: + lat = validate_lat(lat) + except ValueError: + check.record_failed( + "Current Position contains an invalid latitude", + details=f"Invalid latitude: {lat}", + severity=Severity.Medium, + ) + lng = position.lng + try: + lng = validate_lng(lng) + except ValueError: + check.record_failed( + "Current Position contains an invalid longitude", + details=f"Invalid longitude: {lng}", + severity=Severity.Medium, + ) + + def _evaluate_height(self, height: Optional[RIDHeight], participants: List[str]): if height: with self._test_scenario.check( "Height consistency with Common Dictionary", participants diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py index 6e8dc13221..2fefe25c36 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py @@ -207,7 +207,7 @@ def step_under_test(self: UnitTestScenario): rid_version=RIDVersion.f3411_22a, ) - evaluator.evaluate_timestamp(value, []) + evaluator._evaluate_timestamp(value, []) unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome @@ -226,7 +226,7 @@ def step_under_test(self: UnitTestScenario): rid_version=RIDVersion.f3411_22a, ) - evaluator.evaluate_speed(value, []) + evaluator._evaluate_speed(value, []) unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome @@ -245,7 +245,7 @@ def step_under_test(self: UnitTestScenario): rid_version=RIDVersion.f3411_22a, ) - evaluator.evaluate_track(value, []) + evaluator._evaluate_track(value, []) unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome @@ -266,7 +266,7 @@ def step_under_test(self: UnitTestScenario): rid_version=RIDVersion.f3411_22a, ) - evaluator.evaluate_height(value, []) + evaluator._evaluate_height(value, []) unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index 81cf286851..096f89cdd6 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -320,10 +320,6 @@ def _evaluate_observation( verified_sps, ) - if observation: - flights: List[Flight] = observation.get("flights", []) - [self._common_dictionary_evaluator.evaluate_timestamp(f.current_state.timestamp, participants=[observer.participant_id]) for f in flights] - def _evaluate_normal_observation( self, observer: RIDSystemObserver, @@ -386,9 +382,8 @@ def _evaluate_normal_observation( Severity.Medium, details=f"{mapping.injected_flight.uss_participant_id}'s flight with injection ID {mapping.injected_flight.flight.injection_id} in test {mapping.injected_flight.test_id} had telemetry index {mapping.telemetry_index} at {injected_telemetry.timestamp} with lat={injected_telemetry.position.lat}, lng={injected_telemetry.position.lng}, alt={injected_telemetry.position.alt}, but {observer.participant_id} observed lat={observed_position.lat}, lng={observed_position.lng}, alt={observed_position.alt} at {query.request.initiated_at}", ) - observed_speed = mapping.observed_flight.current_state.speed - self._common_dictionary_evaluator.evaluate_speed(observed_speed, [observer.participant_id]) + self._common_dictionary_evaluator.evaluate_dp_flight(mapping.observed_flight, [observer.participant_id]) # Check that flights using telemetry are not using extrapolated position data for mapping in mapping_by_injection_id.values(): injected_telemetry = mapping.injected_flight.flight.telemetry[ diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md index a29450d322..7afadc53aa 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md @@ -212,6 +212,11 @@ This check validates that the display area of a cluster, measured and provided i **[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall (NET0470) provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Operator ID, if present, is valid. (**[astm.f3411.v22a.NET0470,Table1,9](../../../../requirements/astm/f3411/v22a.md)**) +#### Current Position consistency with Common Dictionary check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Current Position provided is valid. (**[astm.f3411.v22a.NET0470,Table1,10](../../../../requirements/astm/f3411/v22a.md)** and **[astm.f3411.v22a.NET0470,Table1,11](../../../../requirements/astm/f3411/v22a.md)**). If the observed Current Position do not contain valid latitude and longitude, this check will fail. +TODO: If the resolution is greater than 7 number digits, this check will fail. + #### Height consistency with Common Dictionary check **[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Track Direction (**[astm.f3411.v22a.NET0470,Table1,14](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Height resolution is less than 1 meter, this check will fail. @@ -226,7 +231,7 @@ This check validates that the display area of a cluster, measured and provided i #### Speed consistency with Common Dictionary check -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Speed (**[astm.f3411.v22a.NET0470,Table1,20](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Speed is greater than 254.25 and different than 255, this check will fail. If the Speed resolution is less than 0.25 m/s, this check will fail. +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Speed (**[astm.f3411.v22a.NET0470,Table1,20](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Speed is negative or greater than 254.25 or equals 255, this check will fail. If the Speed resolution is less than 0.25 m/s, this check will fail. #### Operator Location consistency with Common Dictionary check From 83616565958429c2be31dab43e5b60f97492501f Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Thu, 14 Sep 2023 15:37:07 +0200 Subject: [PATCH 21/37] Fix import --- monitoring/mock_uss/riddp/routes_observation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index c47c8a3471..5b57ef1f3a 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -17,7 +17,7 @@ from .behavior import DisplayProviderBehavior from .config import KEY_RID_VERSION from .database import db -from ...monitorlib.formatting import _limit_resolution +from monitoring.monitorlib.formatting import _limit_resolution def _make_flight_observation( flight: Flight, view: s2sphere.LatLngRect From 903858d188087de01065c770eef173012ab97327 Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Thu, 14 Sep 2023 15:49:10 +0200 Subject: [PATCH 22/37] format --- monitoring/mock_uss/riddp/clustering.py | 4 +- .../mock_uss/riddp/routes_observation.py | 23 +- monitoring/monitorlib/fetch/rid.py | 9 +- monitoring/monitorlib/geo.py | 6 +- .../netrid/common_dictionary_evaluator.py | 94 ++-- .../common_dictionary_evaluator_test.py | 24 +- .../astm/netrid/display_data_evaluator.py | 4 +- .../suites/astm/netrid/f3411_22a.md | 322 ------------- .../suites/uspace/network_identification.md | 317 ------------- .../suites/uspace/required_services.md | 425 ------------------ 10 files changed, 108 insertions(+), 1120 deletions(-) diff --git a/monitoring/mock_uss/riddp/clustering.py b/monitoring/mock_uss/riddp/clustering.py index 78ec8d39a2..110c1c0a77 100644 --- a/monitoring/mock_uss/riddp/clustering.py +++ b/monitoring/mock_uss/riddp/clustering.py @@ -10,7 +10,9 @@ from implicitdict import ImplicitDict from monitoring.monitorlib.rid import RIDVersion -from uas_standards.interuss.automated_testing.rid.v1 import observation as observation_api +from uas_standards.interuss.automated_testing.rid.v1 import ( + observation as observation_api, +) class Point(object): diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index 5b57ef1f3a..c7ff1f0ef8 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -5,12 +5,18 @@ import s2sphere from uas_standards.astm.f3411.v19.api import ErrorResponse from uas_standards.astm.f3411.v19.constants import Scope -from uas_standards.astm.f3411.v22a.constants import MaxSpeed, MinHeightResolution, MinTrackDirectionResolution +from uas_standards.astm.f3411.v22a.constants import ( + MaxSpeed, + MinHeightResolution, + MinTrackDirectionResolution, +) from monitoring.monitorlib import geo from monitoring.monitorlib.fetch import rid as fetch from monitoring.monitorlib.fetch.rid import Flight, FetchedISAs, Position from monitoring.monitorlib.rid import RIDVersion -from uas_standards.interuss.automated_testing.rid.v1 import observation as observation_api +from uas_standards.interuss.automated_testing.rid.v1 import ( + observation as observation_api, +) from monitoring.mock_uss import webapp from monitoring.mock_uss.auth import requires_scope from . import clustering, database, utm_client @@ -19,6 +25,7 @@ from .database import db from monitoring.monitorlib.formatting import _limit_resolution + def _make_flight_observation( flight: Flight, view: s2sphere.LatLngRect ) -> observation_api.Flight: @@ -54,21 +61,25 @@ def _make_flight_observation( paths.append(current_path) p = flight.most_recent_position - original_time = p.time.replace(microsecond=_limit_resolution(p.time.microsecond, pow(10, 5))) + original_time = p.time.replace( + microsecond=_limit_resolution(p.time.microsecond, pow(10, 5)) + ) current_state = observation_api.CurrentState( timestamp=original_time.isoformat(), operational_status=flight.operational_status, track=_limit_resolution(flight.track, MinTrackDirectionResolution), - speed=_limit_resolution(flight.speed, MaxSpeed) + speed=_limit_resolution(flight.speed, MaxSpeed), ) h = p.get("height") if h: h.distance = _limit_resolution(h.distance, MinHeightResolution) return observation_api.Flight( id=flight.id, - most_recent_position=observation_api.Position(lat=p.lat, lng=p.lng, alt=p.alt, height=h), + most_recent_position=observation_api.Position( + lat=p.lat, lng=p.lng, alt=p.alt, height=h + ), recent_paths=[observation_api.Path(positions=path) for path in paths], - current_state=current_state + current_state=current_state, ) diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index 3af2760391..c7e685151a 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -162,7 +162,9 @@ def from_v19_rid_aircraft_position( def from_v22a_rid_aircraft_position( p: v22a.api.RIDAircraftPosition, t: v22a.api.StringBasedDateTime ) -> Position: - return Position(lat=p.lat, lng=p.lng, alt=p.alt, time=t.datetime, height=p.get("height")) + return Position( + lat=p.lat, lng=p.lng, alt=p.alt, time=t.datetime, height=p.get("height") + ) class Flight(ImplicitDict): @@ -239,7 +241,9 @@ def recent_positions(self) -> List[Position]: @property def operational_status(self) -> v22a.api.RIDOperationalStatus: if self.rid_version == RIDVersion.f3411_19: - return v22a.api.RIDOperationalStatus(self.v19_value.current_state.operational_status) + return v22a.api.RIDOperationalStatus( + self.v19_value.current_state.operational_status + ) elif self.rid_version == RIDVersion.f3411_22a: return self.v22a_value.current_state.operational_status else: @@ -408,6 +412,7 @@ def operator_location(self) -> v22a.api.OperatorLocation: f"Cannot retrieve operator_location using RID version {self.rid_version}" ) + class Subscription(ImplicitDict): """Version-independent representation of a F3411 subscription.""" diff --git a/monitoring/monitorlib/geo.py b/monitoring/monitorlib/geo.py index d3543455b8..9b106c8c4f 100644 --- a/monitoring/monitorlib/geo.py +++ b/monitoring/monitorlib/geo.py @@ -52,11 +52,7 @@ class Altitude(ImplicitDict): @staticmethod def w84m(value: float): - return Altitude( - value=value, - reference=AltitudeDatum.W84, - units=DistanceUnits.M - ) + return Altitude(value=value, reference=AltitudeDatum.W84, units=DistanceUnits.M) class Volume3D(ImplicitDict): diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index c11a876015..8ddf3b8da4 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -6,16 +6,28 @@ from uas_standards.interuss.automated_testing.rid.v1.observation import ( GetDetailsResponse, - OperatorAltitudeAltitudeType, RIDHeight, RIDHeightReference, + OperatorAltitudeAltitudeType, + RIDHeight, + RIDHeightReference, ) from uas_standards.ansi_cta_2063_a import SerialNumber from uas_standards.astm.f3411 import v22a -from uas_standards.astm.f3411.v22a.constants import SpecialSpeed, MaxSpeed, MinSpeedResolution, SpecialTrackDirection, MinTrackDirection, MaxTrackDirection, MinTrackDirectionResolution +from uas_standards.astm.f3411.v22a.constants import ( + SpecialSpeed, + MaxSpeed, + MinSpeedResolution, + SpecialTrackDirection, + MinTrackDirection, + MaxTrackDirection, + MinTrackDirectionResolution, +) -from interfaces.uas_standards.src.uas_standards.astm.f3411.v22a.constants import MinHeightResolution, \ - MinPositionResolution +from interfaces.uas_standards.src.uas_standards.astm.f3411.v22a.constants import ( + MinHeightResolution, + MinPositionResolution, +) from monitoring.monitorlib.fetch.rid import ( FetchedFlights, FlightDetails, @@ -84,9 +96,13 @@ def evaluate_dp_flight( self._evaluate_speed(current_state.speed, participants) self._evaluate_track(current_state.track, participants) self._evaluate_timestamp(current_state.timestamp, participants) - self._evaluate_operational_status(current_state.operational_status, participants) + self._evaluate_operational_status( + current_state.operational_status, participants + ) self._evaluate_position(observed_flight.most_recent_position, participants) - self._evaluate_height(observed_flight.most_recent_position.get("height"), participants) + self._evaluate_height( + observed_flight.most_recent_position.get("height"), participants + ) def _evaluate_recent_position_time( self, p: Position, query_time: datetime.datetime, check: PendingCheck @@ -285,7 +301,7 @@ def _evaluate_plain_uas_id(self, value: str, participants: List[str]): def _evaluate_timestamp(self, timestamp: str, participants: List[str]): with self._test_scenario.check( - "Timestamp consistency with Common Dictionary", participants + "Timestamp consistency with Common Dictionary", participants ) as check: try: t = StringBasedDateTime(timestamp) @@ -328,7 +344,7 @@ def _evaluate_operator_id(self, value: Optional[str], participants: List[str]): def _evaluate_speed(self, speed: float, participants: List[str]): with self._test_scenario.check( - "Speed consistency with Common Dictionary", participants + "Speed consistency with Common Dictionary", participants ) as check: if not (0 <= speed <= MaxSpeed or round(speed) == SpecialSpeed): check.record_failed( @@ -346,9 +362,12 @@ def _evaluate_speed(self, speed: float, participants: List[str]): def _evaluate_track(self, track: float, participants: List[str]): with self._test_scenario.check( - "Track Direction consistency with Common Dictionary", participants + "Track Direction consistency with Common Dictionary", participants ) as check: - if not (MinTrackDirection <= track <= MaxTrackDirection or round(track) == SpecialTrackDirection): + if not ( + MinTrackDirection <= track <= MaxTrackDirection + or round(track) == SpecialTrackDirection + ): check.record_failed( f"Invalid track direction: {track}", details=f"The track direction shall be greater than -360 and less than {MaxSpeed}. The Special Value {SpecialSpeed} is allowed.", @@ -362,46 +381,49 @@ def _evaluate_track(self, track: float, participants: List[str]): severity=Severity.Medium, ) - def _evaluate_position( - self, position: Position, participants: List[str] - ): + def _evaluate_position(self, position: Position, participants: List[str]): with self._test_scenario.check( - "Current Position consistency with Common Dictionary", participants + "Current Position consistency with Common Dictionary", participants ) as check: - lat = position.lat - try: - lat = validate_lat(lat) - except ValueError: - check.record_failed( - "Current Position contains an invalid latitude", - details=f"Invalid latitude: {lat}", - severity=Severity.Medium, - ) - lng = position.lng - try: - lng = validate_lng(lng) - except ValueError: - check.record_failed( - "Current Position contains an invalid longitude", - details=f"Invalid longitude: {lng}", - severity=Severity.Medium, - ) + lat = position.lat + try: + lat = validate_lat(lat) + except ValueError: + check.record_failed( + "Current Position contains an invalid latitude", + details=f"Invalid latitude: {lat}", + severity=Severity.Medium, + ) + lng = position.lng + try: + lng = validate_lng(lng) + except ValueError: + check.record_failed( + "Current Position contains an invalid longitude", + details=f"Invalid longitude: {lng}", + severity=Severity.Medium, + ) def _evaluate_height(self, height: Optional[RIDHeight], participants: List[str]): if height: with self._test_scenario.check( - "Height consistency with Common Dictionary", participants + "Height consistency with Common Dictionary", participants ) as check: - if height.distance != _limit_resolution(height.distance, MinHeightResolution): + if height.distance != _limit_resolution( + height.distance, MinHeightResolution + ): check.record_failed( f"Invalid height resolution: {height.distance}", details=f"the height resolution shall not be less than 1 meter", severity=Severity.Medium, ) with self._test_scenario.check( - "Height Type consistency with Common Dictionary", participants + "Height Type consistency with Common Dictionary", participants ) as check: - if height.reference != RIDHeightReference.TakeoffLocation and height.reference != RIDHeightReference.GroundLevel: + if ( + height.reference != RIDHeightReference.TakeoffLocation + and height.reference != RIDHeightReference.GroundLevel + ): check.record_failed( f"Invalid height type: {height.reference}", details=f"The height type reference shall be either {RIDHeightReference.TakeoffLocation} or {RIDHeightReference.GroundLevel}", diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py index 2fefe25c36..c451041bb3 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py @@ -2,7 +2,9 @@ import s2sphere from typing import List, Tuple, Optional from uas_standards.interuss.automated_testing.rid.v1.observation import ( - OperatorAltitudeAltitudeType, RIDHeight, RIDHeightReference, + OperatorAltitudeAltitudeType, + RIDHeight, + RIDHeightReference, ) from uas_standards.astm.f3411.v22a.constants import SpecialTrackDirection @@ -33,6 +35,7 @@ def step_under_test(self: UnitTestScenario): unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome + def test_operator_id_non_ascii(): _assert_operator_id("non_ascii©", False) @@ -212,12 +215,14 @@ def step_under_test(self: UnitTestScenario): unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome + def test_timestamp(): _assert_timestamp("2023-09-13T04:43:00.1Z", True) # Ok _assert_timestamp("2023-09-13T04:43:00Z", True) # Ok _assert_timestamp("2023-09-13T04:43:00.501Z", False) # Wrong resolution _assert_timestamp("2023-09-13T04:43:00.1EST", False) # Wrong timezone + def _assert_speed(value: float, outcome: bool): def step_under_test(self: UnitTestScenario): evaluator = RIDCommonDictionaryEvaluator( @@ -231,12 +236,14 @@ def step_under_test(self: UnitTestScenario): unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome + def test_speed(): _assert_speed(1, True) # Ok _assert_speed(20.75, True) # Ok _assert_speed(400, False) # Fail, above MaxSpeed _assert_speed(23.3, False) # Wrong resolution + def _assert_track(value: float, outcome: bool): def step_under_test(self: UnitTestScenario): evaluator = RIDCommonDictionaryEvaluator( @@ -250,6 +257,7 @@ def step_under_test(self: UnitTestScenario): unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome + def test_track(): _assert_track(1, True) # Ok _assert_track(-359, True) # Ok @@ -258,6 +266,7 @@ def test_track(): _assert_track(23.3, False) # Wrong resolution _assert_track(SpecialTrackDirection, True) + def _assert_height(value: RIDHeight, outcome: bool): def step_under_test(self: UnitTestScenario): evaluator = RIDCommonDictionaryEvaluator( @@ -268,15 +277,20 @@ def step_under_test(self: UnitTestScenario): evaluator._evaluate_height(value, []) - unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome + def test_height(): - _assert_height(None, True) # Ok + _assert_height(None, True) # Ok _assert_height(RIDHeight(distance=10, reference="TakeoffLocation"), True) # Ok - _assert_height(RIDHeight(distance=10.101, reference="TakeoffLocation"), False) # Wrong resolution - _assert_height(RIDHeight(distance=10.101, reference="Moon"), False) # Wrong reference + _assert_height( + RIDHeight(distance=10.101, reference="TakeoffLocation"), False + ) # Wrong resolution + _assert_height( + RIDHeight(distance=10.101, reference="Moon"), False + ) # Wrong reference + def _assert_evaluate_sp_flight_recent_positions( f: Flight, query_time: datetime, outcome: bool diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index 096f89cdd6..b3a4b5fb74 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -383,7 +383,9 @@ def _evaluate_normal_observation( details=f"{mapping.injected_flight.uss_participant_id}'s flight with injection ID {mapping.injected_flight.flight.injection_id} in test {mapping.injected_flight.test_id} had telemetry index {mapping.telemetry_index} at {injected_telemetry.timestamp} with lat={injected_telemetry.position.lat}, lng={injected_telemetry.position.lng}, alt={injected_telemetry.position.alt}, but {observer.participant_id} observed lat={observed_position.lat}, lng={observed_position.lng}, alt={observed_position.alt} at {query.request.initiated_at}", ) - self._common_dictionary_evaluator.evaluate_dp_flight(mapping.observed_flight, [observer.participant_id]) + self._common_dictionary_evaluator.evaluate_dp_flight( + mapping.observed_flight, [observer.participant_id] + ) # Check that flights using telemetry are not using extrapolated position data for mapping in mapping_by_injection_id.values(): injected_telemetry = mapping.injected_flight.flight.telemetry[ diff --git a/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md b/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md index 96bddbbf33..e69de29bb2 100644 --- a/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md +++ b/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md @@ -1,322 +0,0 @@ - -# ASTM F3411-22a test suite -[`suites.astm.netrid.f3411_22a`](./f3411_22a.yaml) - -## Actions - -1. Action generator: [`action_generators.astm.f3411.ForEachDSS`](../../../action_generators/astm/f3411/for_each_dss.py) - 1. Suite: [DSS instance probing for ASTM NetRID F3411-22a](f3411_22a/dss_probing.md) ([`suites.astm.netrid.f3411_22a.dss_probing`](f3411_22a/dss_probing.yaml)) -2. Scenario: [ASTM F3411-22a NetRID DSS interoperability](../../../scenarios/astm/netrid/v22a/dss_interoperability.md) ([`scenarios.astm.netrid.v22a.DSSInteroperability`](../../../scenarios/astm/netrid/v22a/dss_interoperability.py)) -3. Scenario: [ASTM NetRID nominal behavior](../../../scenarios/astm/netrid/v22a/nominal_behavior.md) ([`scenarios.astm.netrid.v22a.NominalBehavior`](../../../scenarios/astm/netrid/v22a/nominal_behavior.py)) -4. Scenario: [ASTM NetRID SP clients misbehavior handling](../../../scenarios/astm/netrid/v22a/misbehavior.md) ([`scenarios.astm.netrid.v22a.Misbehavior`](../../../scenarios/astm/netrid/v22a/misbehavior.py)) -4. Scenario: [ASTM F3411-22a NetRID aggregate checks](../../../scenarios/astm/netrid/v22a/aggregate_checks.md) ([`scenarios.astm.netrid.v22a.AggregateChecks`](../../../scenarios/astm/netrid/v22a/aggregate_checks.py)) - -## Checked requirements - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PackageRequirementStatusChecked in
astm
.f3411
.v19
NET0220ImplementedASTM F3411-22a NetRID aggregate checks
astm
.f3411
.v22a
A2-6-1,1aImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,1bImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,1cImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,1dImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,2aImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,2bImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,3aImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,3bImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,3cIn progressASTM F3411-22a NetRID DSS interoperability
A2-6-1,3dIn progressASTM F3411-22a NetRID DSS interoperability
A2-6-1,4aImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,4bImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,5ImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,6ImplementedASTM F3411-22a NetRID DSS interoperability
DSS0030ImplementedASTM NetRID DSS: Simple ISA
ASTM NetRID DSS: Subscription Validation
ASTM NetRID nominal behavior
DSS0050ImplementedASTM NetRID DSS: Subscription Validation
DSS0060ImplementedASTM NetRID DSS: Subscription Validation
DSS0070ImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130ImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,aImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,bImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,cImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,dImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,fImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,3,aImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,3,bImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,3,cImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,3,dImplementedASTM F3411-22a NetRID DSS interoperability
DSS0210ImplementedASTM F3411-22a NetRID DSS interoperability
NET0240PlannedASTM F3411-22a NetRID aggregate checks
NET0260ImplementedASTM NetRID nominal behavior
NET0260,Table1,1ImplementedASTM NetRID nominal behavior
NET0260,Table1,1aImplementedASTM NetRID nominal behavior
NET0260,Table1,23ImplementedASTM NetRID nominal behavior
NET0260,Table1,24ImplementedASTM NetRID nominal behavior
NET0260,Table1,25ImplementedASTM NetRID nominal behavior
NET0260,Table1,26ImplementedASTM NetRID nominal behavior
NET0260,Table1,7ImplementedASTM NetRID nominal behavior
NET0260,Table1,9ImplementedASTM NetRID nominal behavior
NET0260-aImplementedASTM F3411-22a NetRID aggregate checks
NET0270ImplementedASTM NetRID nominal behavior
NET0290ImplementedASTM NetRID nominal behavior
NET0420ImplementedASTM F3411-22a NetRID aggregate checks
NET0430ImplementedASTM NetRID nominal behavior
NET0440ImplementedASTM F3411-22a NetRID aggregate checks
NET0470ImplementedASTM NetRID nominal behavior
NET0480ImplementedASTM NetRID nominal behavior
NET0490ImplementedASTM NetRID nominal behavior
NET0500ImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
NET0610ImplementedASTM NetRID nominal behavior
NET0710ImplementedASTM NetRID DSS: Simple ISA
ASTM NetRID nominal behavior
NET0730ImplementedASTM NetRID DSS: Simple ISA
interuss
.automated_testing
.rid
.injection
DeleteTestSuccessImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
ExpectedBehaviorImplementedASTM NetRID nominal behavior
UpsertTestResultPlannedASTM NetRID nominal behavior
UpsertTestSuccessImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
interuss
.automated_testing
.rid
.observation
ObservationSuccessImplementedASTM NetRID nominal behavior
UniqueFlightsImplementedASTM NetRID nominal behavior
diff --git a/monitoring/uss_qualifier/suites/uspace/network_identification.md b/monitoring/uss_qualifier/suites/uspace/network_identification.md index 45065fac1f..e69de29bb2 100644 --- a/monitoring/uss_qualifier/suites/uspace/network_identification.md +++ b/monitoring/uss_qualifier/suites/uspace/network_identification.md @@ -1,317 +0,0 @@ - -# U-Space network identification test suite -[`suites.uspace.network_identification`](./network_identification.yaml) - -## Actions - -1. Suite: [ASTM F3411-22a](../astm/netrid/f3411_22a.md) ([`suites.astm.netrid.f3411_22a`](../astm/netrid/f3411_22a.yaml)) - -## Checked requirements - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PackageRequirementStatusChecked in
astm
.f3411
.v19
NET0220ImplementedASTM F3411-22a NetRID aggregate checks
astm
.f3411
.v22a
A2-6-1,1aImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,1bImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,1cImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,1dImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,2aImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,2bImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,3aImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,3bImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,3cIn progressASTM F3411-22a NetRID DSS interoperability
A2-6-1,3dIn progressASTM F3411-22a NetRID DSS interoperability
A2-6-1,4aImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,4bImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,5ImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,6ImplementedASTM F3411-22a NetRID DSS interoperability
DSS0030ImplementedASTM NetRID DSS: Simple ISA
ASTM NetRID DSS: Subscription Validation
ASTM NetRID nominal behavior
DSS0050ImplementedASTM NetRID DSS: Subscription Validation
DSS0060ImplementedASTM NetRID DSS: Subscription Validation
DSS0070ImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130ImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,aImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,bImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,cImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,dImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,fImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,3,aImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,3,bImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,3,cImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,3,dImplementedASTM F3411-22a NetRID DSS interoperability
DSS0210ImplementedASTM F3411-22a NetRID DSS interoperability
NET0240PlannedASTM F3411-22a NetRID aggregate checks
NET0260ImplementedASTM NetRID nominal behavior
NET0260,Table1,1ImplementedASTM NetRID nominal behavior
NET0260,Table1,1aImplementedASTM NetRID nominal behavior
NET0260,Table1,23ImplementedASTM NetRID nominal behavior
NET0260,Table1,24ImplementedASTM NetRID nominal behavior
NET0260,Table1,25ImplementedASTM NetRID nominal behavior
NET0260,Table1,26ImplementedASTM NetRID nominal behavior
NET0260,Table1,7ImplementedASTM NetRID nominal behavior
NET0260,Table1,9ImplementedASTM NetRID nominal behavior
NET0260-aImplementedASTM F3411-22a NetRID aggregate checks
NET0270ImplementedASTM NetRID nominal behavior
NET0290ImplementedASTM NetRID nominal behavior
NET0420ImplementedASTM F3411-22a NetRID aggregate checks
NET0430ImplementedASTM NetRID nominal behavior
NET0440ImplementedASTM F3411-22a NetRID aggregate checks
NET0470ImplementedASTM NetRID nominal behavior
NET0480ImplementedASTM NetRID nominal behavior
NET0490ImplementedASTM NetRID nominal behavior
NET0500ImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
NET0610ImplementedASTM NetRID nominal behavior
NET0710ImplementedASTM NetRID DSS: Simple ISA
ASTM NetRID nominal behavior
NET0730ImplementedASTM NetRID DSS: Simple ISA
interuss
.automated_testing
.rid
.injection
DeleteTestSuccessImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
ExpectedBehaviorImplementedASTM NetRID nominal behavior
UpsertTestResultPlannedASTM NetRID nominal behavior
UpsertTestSuccessImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
interuss
.automated_testing
.rid
.observation
ObservationSuccessImplementedASTM NetRID nominal behavior
UniqueFlightsImplementedASTM NetRID nominal behavior
diff --git a/monitoring/uss_qualifier/suites/uspace/required_services.md b/monitoring/uss_qualifier/suites/uspace/required_services.md index 54f85ab0c0..e69de29bb2 100644 --- a/monitoring/uss_qualifier/suites/uspace/required_services.md +++ b/monitoring/uss_qualifier/suites/uspace/required_services.md @@ -1,425 +0,0 @@ - -# U-space required services test suite -[`suites.uspace.required_services`](./required_services.yaml) - -## Actions - -1. Suite: [U-space flight authorisation](flight_auth.md) ([`suites.uspace.flight_auth`](flight_auth.yaml)) -2. Suite: [U-Space network identification](network_identification.md) ([`suites.uspace.network_identification`](network_identification.yaml)) - -## Checked requirements - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PackageRequirementStatusChecked in
astm
.f3411
.v19
NET0220ImplementedASTM F3411-22a NetRID aggregate checks
astm
.f3411
.v22a
A2-6-1,1aImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,1bImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,1cImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,1dImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,2aImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,2bImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,3aImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,3bImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,3cIn progressASTM F3411-22a NetRID DSS interoperability
A2-6-1,3dIn progressASTM F3411-22a NetRID DSS interoperability
A2-6-1,4aImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,4bImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,5ImplementedASTM F3411-22a NetRID DSS interoperability
A2-6-1,6ImplementedASTM F3411-22a NetRID DSS interoperability
DSS0030ImplementedASTM NetRID DSS: Simple ISA
ASTM NetRID DSS: Subscription Validation
ASTM NetRID nominal behavior
DSS0050ImplementedASTM NetRID DSS: Subscription Validation
DSS0060ImplementedASTM NetRID DSS: Subscription Validation
DSS0070ImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130ImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,aImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,bImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,cImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,dImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,2,fImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,3,aImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,3,bImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,3,cImplementedASTM F3411-22a NetRID DSS interoperability
DSS0130,3,dImplementedASTM F3411-22a NetRID DSS interoperability
DSS0210ImplementedASTM F3411-22a NetRID DSS interoperability
NET0240PlannedASTM F3411-22a NetRID aggregate checks
NET0260ImplementedASTM NetRID nominal behavior
NET0260,Table1,1ImplementedASTM NetRID nominal behavior
NET0260,Table1,1aImplementedASTM NetRID nominal behavior
NET0260,Table1,23ImplementedASTM NetRID nominal behavior
NET0260,Table1,24ImplementedASTM NetRID nominal behavior
NET0260,Table1,25ImplementedASTM NetRID nominal behavior
NET0260,Table1,26ImplementedASTM NetRID nominal behavior
NET0260,Table1,7ImplementedASTM NetRID nominal behavior
NET0260,Table1,9ImplementedASTM NetRID nominal behavior
NET0260-aImplementedASTM F3411-22a NetRID aggregate checks
NET0270ImplementedASTM NetRID nominal behavior
NET0290ImplementedASTM NetRID nominal behavior
NET0420ImplementedASTM F3411-22a NetRID aggregate checks
NET0430ImplementedASTM NetRID nominal behavior
NET0440ImplementedASTM F3411-22a NetRID aggregate checks
NET0470ImplementedASTM NetRID nominal behavior
NET0480ImplementedASTM NetRID nominal behavior
NET0490ImplementedASTM NetRID nominal behavior
NET0500ImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
NET0610ImplementedASTM NetRID nominal behavior
NET0710ImplementedASTM NetRID DSS: Simple ISA
ASTM NetRID nominal behavior
NET0730ImplementedASTM NetRID DSS: Simple ISA
astm
.f3548
.v21
DSS0005ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
GEN0310ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
GEN0500ImplementedValidation of operational intents
OPIN0015ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
OPIN0020ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
OPIN0025ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
OPIN0030ImplementedValidation of operational intents
OPIN0040ImplementedValidation of operational intents
SCD0015ImplementedNominal planning: conflict with higher priority
SCD0020ImplementedNominal planning: conflict with higher priority
SCD0025ImplementedNominal planning: conflict with higher priority
SCD0030ImplementedNominal planning: conflict with higher priority
SCD0035ImplementedNominal planning: not permitted conflict with equal priority
SCD0040ImplementedNominal planning: not permitted conflict with equal priority
SCD0045ImplementedNominal planning: not permitted conflict with equal priority
SCD0050ImplementedNominal planning: not permitted conflict with equal priority
USS0005ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
USS0105ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
interuss
.automated_testing
.flight_planning
ClearAreaImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
DeleteFlightSuccessImplementedFlight authorisation validation
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
ExpectedBehaviorImplementedFlight authorisation validation
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
interuss
.automated_testing
.rid
.injection
DeleteTestSuccessImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
ExpectedBehaviorImplementedASTM NetRID nominal behavior
UpsertTestResultPlannedASTM NetRID nominal behavior
UpsertTestSuccessImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
interuss
.automated_testing
.rid
.observation
ObservationSuccessImplementedASTM NetRID nominal behavior
UniqueFlightsImplementedASTM NetRID nominal behavior
From 2e84910b5d11643358191f2b50e1ee34b6826b22 Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Thu, 14 Sep 2023 20:38:19 +0200 Subject: [PATCH 23/37] Rename to _evaluate_arbitrary_uas_id and skip some tests if not v22a --- .../netrid/common_dictionary_evaluator.py | 280 ++++++++++-------- 1 file changed, 161 insertions(+), 119 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index 8ddf3b8da4..13028758e5 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -209,7 +209,7 @@ def evaluate_dp_details( if not observed_details: observed_details = {} - self._evaluate_plain_uas_id( + self._evaluate_arbitrary_uas_id( observed_details.get("uas", {}).get("id"), participants ) @@ -277,158 +277,200 @@ def _evaluate_uas_id( message=f"Unsupported version {self._rid_version}: skipping UAS ID evaluation", ) - def _evaluate_plain_uas_id(self, value: str, participants: List[str]): - with self._test_scenario.check( - "UAS ID presence in flight details", participants - ) as check: - if not value: - check.record_failed( - f"UAS ID not present as required by the Common Dictionary definition: {value}", - severity=Severity.Medium, - ) - return + def _evaluate_arbitrary_uas_id(self, value: str, participants: List[str]): + if self._rid_version == RIDVersion.f3411_22a: + with self._test_scenario.check( + "UAS ID presence in flight details", participants + ) as check: + if not value: + check.record_failed( + f"UAS ID not present as required by the Common Dictionary definition: {value}", + severity=Severity.Medium, + ) + return - if SerialNumber(value).valid: - self._test_scenario.check( - "UAS ID (Serial Number format) consistency with Common Dictionary", - participants, - ).record_passed(participants) + if SerialNumber(value).valid: + self._test_scenario.check( + "UAS ID (Serial Number format) consistency with Common Dictionary", + participants, + ).record_passed(participants) # TODO: Add registration id format check # TODO: Add utm id format check # TODO: Add specific session id format check # TODO: Add a check to validate at least one format is correct + else: + self._test_scenario.record_note( + key="skip_reason", + message=f"Unsupported version {self._rid_version}: skipping arbitrary uas id evaluation", + ) def _evaluate_timestamp(self, timestamp: str, participants: List[str]): - with self._test_scenario.check( - "Timestamp consistency with Common Dictionary", participants - ) as check: - try: - t = StringBasedDateTime(timestamp) - if t.datetime.utcoffset().seconds != 0: - check.record_failed( - f"Timestamp must be relative to UTC: {timestamp}", - severity=Severity.Medium, - ) - ms = t.datetime.microsecond - ms_res = _limit_resolution(ms, pow(10, 5)) - if ms != ms_res: + if self._rid_version == RIDVersion.f3411_22a: + with self._test_scenario.check( + "Timestamp consistency with Common Dictionary", participants + ) as check: + try: + t = StringBasedDateTime(timestamp) + if t.datetime.utcoffset().seconds != 0: + check.record_failed( + f"Timestamp must be relative to UTC: {timestamp}", + severity=Severity.Medium, + ) + ms = t.datetime.microsecond + ms_res = _limit_resolution(ms, pow(10, 5)) + if ms != ms_res: + check.record_failed( + f"Timestamp resolution is smaller than 1/10: {timestamp}", + severity=Severity.Medium, + ) + except ParserError as e: check.record_failed( - f"Timestamp resolution is smaller than 1/10: {timestamp}", + f"Unable to parse timestamp: {timestamp}", + details=f"Reason: {e}", severity=Severity.Medium, ) - except ParserError as e: - check.record_failed( - f"Unable to parse timestamp: {timestamp}", - details=f"Reason: {e}", - severity=Severity.Medium, - ) + else: + self._test_scenario.record_note( + key="skip_reason", + message=f"Unsupported version {self._rid_version}: skipping timestamp evaluation", + ) def _evaluate_operator_id(self, value: Optional[str], participants: List[str]): if self._rid_version == RIDVersion.f3411_22a: - if value: - with self._test_scenario.check( - "Operator ID consistency with Common Dictionary", participants - ) as check: - is_ascii = all([0 <= ord(c) < 128 for c in value]) - if not is_ascii: - check.record_failed( - "Operator ID contains non-ascii characters", - severity=Severity.Medium, - ) + if self._rid_version == RIDVersion.f3411_22a: + if value: + with self._test_scenario.check( + "Operator ID consistency with Common Dictionary", participants + ) as check: + is_ascii = all([0 <= ord(c) < 128 for c in value]) + if not is_ascii: + check.record_failed( + "Operator ID contains non-ascii characters", + severity=Severity.Medium, + ) + else: + self._test_scenario.record_note( + key="skip_reason", + message=f"Unsupported version {self._rid_version}: skipping Operator ID evaluation", + ) else: self._test_scenario.record_note( key="skip_reason", - message=f"Unsupported version {self._rid_version}: skipping Operator ID evaluation", + message=f"Unsupported version {self._rid_version}: skipping operator id evaluation", ) def _evaluate_speed(self, speed: float, participants: List[str]): - with self._test_scenario.check( - "Speed consistency with Common Dictionary", participants - ) as check: - if not (0 <= speed <= MaxSpeed or round(speed) == SpecialSpeed): - check.record_failed( - f"Invalid speed: {speed}", - details=f"The speed shall be greater than 0 and less than {MaxSpeed}. The Special Value {SpecialSpeed} is allowed.", - severity=Severity.Medium, - ) + if self._rid_version == RIDVersion.f3411_22a: + with self._test_scenario.check( + "Speed consistency with Common Dictionary", participants + ) as check: + if not (0 <= speed <= MaxSpeed or round(speed) == SpecialSpeed): + check.record_failed( + f"Invalid speed: {speed}", + details=f"The speed shall be greater than 0 and less than {MaxSpeed}. The Special Value {SpecialSpeed} is allowed.", + severity=Severity.Medium, + ) - if speed != _limit_resolution(speed, MinSpeedResolution): - check.record_failed( - f"Invalid speed resolution: {speed}", - details=f"the speed resolution shall not be less than 0.25 m/s", - severity=Severity.Medium, - ) + if speed != _limit_resolution(speed, MinSpeedResolution): + check.record_failed( + f"Invalid speed resolution: {speed}", + details=f"the speed resolution shall not be less than 0.25 m/s", + severity=Severity.Medium, + ) + else: + self._test_scenario.record_note( + key="skip_reason", + message=f"Unsupported version {self._rid_version}: skipping speed evaluation", + ) def _evaluate_track(self, track: float, participants: List[str]): - with self._test_scenario.check( - "Track Direction consistency with Common Dictionary", participants - ) as check: - if not ( - MinTrackDirection <= track <= MaxTrackDirection - or round(track) == SpecialTrackDirection - ): - check.record_failed( - f"Invalid track direction: {track}", - details=f"The track direction shall be greater than -360 and less than {MaxSpeed}. The Special Value {SpecialSpeed} is allowed.", - severity=Severity.Medium, - ) - - if track != _limit_resolution(track, MinTrackDirectionResolution): - check.record_failed( - f"Invalid track direction resolution: {track}", - details=f"the track direction resolution shall not be less than 1 degree", - severity=Severity.Medium, - ) - - def _evaluate_position(self, position: Position, participants: List[str]): - with self._test_scenario.check( - "Current Position consistency with Common Dictionary", participants - ) as check: - lat = position.lat - try: - lat = validate_lat(lat) - except ValueError: - check.record_failed( - "Current Position contains an invalid latitude", - details=f"Invalid latitude: {lat}", - severity=Severity.Medium, - ) - lng = position.lng - try: - lng = validate_lng(lng) - except ValueError: - check.record_failed( - "Current Position contains an invalid longitude", - details=f"Invalid longitude: {lng}", - severity=Severity.Medium, - ) - - def _evaluate_height(self, height: Optional[RIDHeight], participants: List[str]): - if height: + if self._rid_version == RIDVersion.f3411_22a: with self._test_scenario.check( - "Height consistency with Common Dictionary", participants + "Track Direction consistency with Common Dictionary", participants ) as check: - if height.distance != _limit_resolution( - height.distance, MinHeightResolution + if not ( + MinTrackDirection <= track <= MaxTrackDirection + or round(track) == SpecialTrackDirection ): check.record_failed( - f"Invalid height resolution: {height.distance}", - details=f"the height resolution shall not be less than 1 meter", + f"Invalid track direction: {track}", + details=f"The track direction shall be greater than -360 and less than {MaxSpeed}. The Special Value {SpecialSpeed} is allowed.", + severity=Severity.Medium, + ) + + if track != _limit_resolution(track, MinTrackDirectionResolution): + check.record_failed( + f"Invalid track direction resolution: {track}", + details=f"the track direction resolution shall not be less than 1 degree", severity=Severity.Medium, ) + else: + self._test_scenario.record_note( + key="skip_reason", + message=f"Unsupported version {self._rid_version}: skipping track direction evaluation", + ) + + def _evaluate_position(self, position: Position, participants: List[str]): + if self._rid_version == RIDVersion.f3411_22a: with self._test_scenario.check( - "Height Type consistency with Common Dictionary", participants + "Current Position consistency with Common Dictionary", participants ) as check: - if ( - height.reference != RIDHeightReference.TakeoffLocation - and height.reference != RIDHeightReference.GroundLevel - ): + lat = position.lat + try: + lat = validate_lat(lat) + except ValueError: + check.record_failed( + "Current Position contains an invalid latitude", + details=f"Invalid latitude: {lat}", + severity=Severity.Medium, + ) + lng = position.lng + try: + lng = validate_lng(lng) + except ValueError: check.record_failed( - f"Invalid height type: {height.reference}", - details=f"The height type reference shall be either {RIDHeightReference.TakeoffLocation} or {RIDHeightReference.GroundLevel}", + "Current Position contains an invalid longitude", + details=f"Invalid longitude: {lng}", severity=Severity.Medium, ) + else: + self._test_scenario.record_note( + key="skip_reason", + message=f"Unsupported version {self._rid_version}: skipping position evaluation", + ) + + def _evaluate_height(self, height: Optional[RIDHeight], participants: List[str]): + if self._rid_version == RIDVersion.f3411_22a: + if height: + with self._test_scenario.check( + "Height consistency with Common Dictionary", participants + ) as check: + if height.distance != _limit_resolution( + height.distance, MinHeightResolution + ): + check.record_failed( + f"Invalid height resolution: {height.distance}", + details=f"the height resolution shall not be less than 1 meter", + severity=Severity.Medium, + ) + with self._test_scenario.check( + "Height Type consistency with Common Dictionary", participants + ) as check: + if ( + height.reference != RIDHeightReference.TakeoffLocation + and height.reference != RIDHeightReference.GroundLevel + ): + check.record_failed( + f"Invalid height type: {height.reference}", + details=f"The height type reference shall be either {RIDHeightReference.TakeoffLocation} or {RIDHeightReference.GroundLevel}", + severity=Severity.Medium, + ) + else: + self._test_scenario.record_note( + key="skip_reason", + message=f"Unsupported version {self._rid_version}: skipping Height evaluation", + ) def _evaluate_operator_location( self, From 0c29941b4128a5ef0c2c2863399a999be83964ab Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Thu, 14 Sep 2023 21:19:54 +0200 Subject: [PATCH 24/37] Clean up --- .../mock_uss/riddp/routes_observation.py | 10 +++-- monitoring/monitorlib/fetch/rid.py | 16 ++++---- monitoring/monitorlib/geo.py | 4 +- .../netrid/common_dictionary_evaluator.py | 40 ++++++++----------- 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index c7ff1f0ef8..46d900306e 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -61,11 +61,11 @@ def _make_flight_observation( paths.append(current_path) p = flight.most_recent_position - original_time = p.time.replace( + timestamp = p.time.replace( microsecond=_limit_resolution(p.time.microsecond, pow(10, 5)) ) current_state = observation_api.CurrentState( - timestamp=original_time.isoformat(), + timestamp=timestamp.isoformat(), operational_status=flight.operational_status, track=_limit_resolution(flight.track, MinTrackDirectionResolution), speed=_limit_resolution(flight.speed, MaxSpeed), @@ -191,7 +191,9 @@ def riddp_flight_details(flight_id: str) -> Tuple[str, int]: ) if rid_version == RIDVersion.f3411_19: # TODO: Implement details for F3411-19 - return flask.jsonify(observation_api.GetDetailsResponse(id=flight_id)) + return flask.jsonify(observation_api.GetDetailsResponse( + operator=observation_api.Operator() + )) elif rid_version == RIDVersion.f3411_22a: details = flight_details.details result = observation_api.GetDetailsResponse( @@ -204,7 +206,7 @@ def riddp_flight_details(flight_id: str) -> Tuple[str, int]: ), ), uas=observation_api.UAS( - id=details.plain_uas_id, + id=details.arbitrary_uas_id, ), ) return flask.jsonify(result) diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index c7e685151a..3fa045b07d 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -216,7 +216,7 @@ def most_recent_position( ) else: raise NotImplementedError( - f"Cannot retrieve most recent position using RID version {self.rid_version}" + f"Cannot retrieve most_recent_position using RID version {self.rid_version}" ) else: return None @@ -235,7 +235,7 @@ def recent_positions(self) -> List[Position]: ] else: raise NotImplementedError( - f"Cannot retrieve recent positions using RID version {self.rid_version}" + f"Cannot retrieve recent_positions using RID version {self.rid_version}" ) @property @@ -248,7 +248,7 @@ def operational_status(self) -> v22a.api.RIDOperationalStatus: return self.v22a_value.current_state.operational_status else: raise NotImplementedError( - f"Cannot retrieve operational status using RID version {self.rid_version}" + f"Cannot retrieve operational_status using RID version {self.rid_version}" ) @property @@ -270,7 +270,7 @@ def speed(self): return self.v22a_value.current_state.speed else: raise NotImplementedError( - f"Cannot retrieve track using RID version {self.rid_version}" + f"Cannot retrieve speed using RID version {self.rid_version}" ) def errors(self) -> List[str]: @@ -373,7 +373,7 @@ def operator_id(self) -> str: ) @property - def plain_uas_id(self) -> Optional[str]: + def arbitrary_uas_id(self) -> Optional[str]: """Returns a UAS id as a plain string without type hint. If multiple are provided: For v19, registration_number is returned if set, else it falls back to the serial_number. @@ -381,9 +381,9 @@ def plain_uas_id(self) -> Optional[str]: If no match, it returns None. """ if self.rid_version == RIDVersion.f3411_19: - rn = self.v19_value.registration_number - if rn: - return rn + registration_number = self.v19_value.registration_number + if registration_number: + return registration_number else: return self.v19_value.serial_number elif self.rid_version == RIDVersion.f3411_22a: diff --git a/monitoring/monitorlib/geo.py b/monitoring/monitorlib/geo.py index 9b106c8c4f..ef002d6c60 100644 --- a/monitoring/monitorlib/geo.py +++ b/monitoring/monitorlib/geo.py @@ -51,7 +51,9 @@ class Altitude(ImplicitDict): units: DistanceUnits @staticmethod - def w84m(value: float): + def w84m(value: Optional[float]): + if not value: + return None return Altitude(value=value, reference=AltitudeDatum.W84, units=DistanceUnits.M) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index 13028758e5..7fc9a69c06 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -24,7 +24,7 @@ MinTrackDirectionResolution, ) -from interfaces.uas_standards.src.uas_standards.astm.f3411.v22a.constants import ( +from uas_standards.astm.f3411.v22a.constants import ( MinHeightResolution, MinPositionResolution, ) @@ -207,7 +207,7 @@ def evaluate_dp_details( self, observed_details: Optional[GetDetailsResponse], participants: List[str] ): if not observed_details: - observed_details = {} + return self._evaluate_arbitrary_uas_id( observed_details.get("uas", {}).get("id"), participants @@ -221,9 +221,7 @@ def evaluate_dp_details( operator_altitude_value = operator_altitude.get("altitude") self._evaluate_operator_location( operator_location, - Altitude.w84m(value=operator_altitude_value) - if operator_altitude_value - else None, + Altitude.w84m(value=operator_altitude_value), operator_altitude.get("altitude_type"), participants, ) @@ -321,7 +319,7 @@ def _evaluate_timestamp(self, timestamp: str, participants: List[str]): ms_res = _limit_resolution(ms, pow(10, 5)) if ms != ms_res: check.record_failed( - f"Timestamp resolution is smaller than 1/10: {timestamp}", + f"Timestamp resolution is smaller than 1/10 second: {timestamp}", severity=Severity.Medium, ) except ParserError as e: @@ -338,22 +336,16 @@ def _evaluate_timestamp(self, timestamp: str, participants: List[str]): def _evaluate_operator_id(self, value: Optional[str], participants: List[str]): if self._rid_version == RIDVersion.f3411_22a: - if self._rid_version == RIDVersion.f3411_22a: - if value: - with self._test_scenario.check( - "Operator ID consistency with Common Dictionary", participants - ) as check: - is_ascii = all([0 <= ord(c) < 128 for c in value]) - if not is_ascii: - check.record_failed( - "Operator ID contains non-ascii characters", - severity=Severity.Medium, - ) - else: - self._test_scenario.record_note( - key="skip_reason", - message=f"Unsupported version {self._rid_version}: skipping Operator ID evaluation", - ) + if value: + with self._test_scenario.check( + "Operator ID consistency with Common Dictionary", participants + ) as check: + is_ascii = all([0 <= ord(c) < 128 for c in value]) + if not is_ascii: + check.record_failed( + "Operator ID contains non-ascii characters", + severity=Severity.Medium, + ) else: self._test_scenario.record_note( key="skip_reason", @@ -421,7 +413,7 @@ def _evaluate_position(self, position: Position, participants: List[str]): lat = validate_lat(lat) except ValueError: check.record_failed( - "Current Position contains an invalid latitude", + "Current Position contains an invalid latitude", details=f"Invalid latitude: {lat}", severity=Severity.Medium, ) @@ -485,7 +477,7 @@ def _evaluate_operator_location( ) as check: if not position: check.record_failed( - "Missing Operator position", + "Missing Operator Location position", details=f"Invalid position: {position}", severity=Severity.Medium, ) From cbb0bc14e4a395f6169bd578beb0a16a35dbbe1d Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Thu, 14 Sep 2023 21:26:50 +0200 Subject: [PATCH 25/37] format --- monitoring/mock_uss/riddp/routes_observation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index 46d900306e..94ed617635 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -191,9 +191,9 @@ def riddp_flight_details(flight_id: str) -> Tuple[str, int]: ) if rid_version == RIDVersion.f3411_19: # TODO: Implement details for F3411-19 - return flask.jsonify(observation_api.GetDetailsResponse( - operator=observation_api.Operator() - )) + return flask.jsonify( + observation_api.GetDetailsResponse(operator=observation_api.Operator()) + ) elif rid_version == RIDVersion.f3411_22a: details = flight_details.details result = observation_api.GetDetailsResponse( From ac1353a5d7ec6635c5a2dba20042afeb575cd1a7 Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Thu, 14 Sep 2023 22:06:33 +0200 Subject: [PATCH 26/37] Update doc --- .../suites/uspace/network_identification.md | 247 ++++++++++++ .../suites/uspace/required_services.md | 355 ++++++++++++++++++ 2 files changed, 602 insertions(+) diff --git a/monitoring/uss_qualifier/suites/uspace/network_identification.md b/monitoring/uss_qualifier/suites/uspace/network_identification.md index e69de29bb2..3871b57a6f 100644 --- a/monitoring/uss_qualifier/suites/uspace/network_identification.md +++ b/monitoring/uss_qualifier/suites/uspace/network_identification.md @@ -0,0 +1,247 @@ + +# U-Space network identification test suite +[`suites.uspace.network_identification`](./network_identification.yaml) + +## Actions + +1. Suite: [ASTM F3411-22a](../astm/netrid/f3411_22a.md) ([`suites.astm.netrid.f3411_22a`](../astm/netrid/f3411_22a.yaml)) + +## Checked requirements + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PackageRequirementStatusChecked in
astm
.f3411
.v19
NET0220ImplementedASTM F3411-22a NetRID aggregate checks
astm
.f3411
.v22a
DSS0030ImplementedASTM NetRID nominal behavior
NET0240PlannedASTM F3411-22a NetRID aggregate checks
NET0260ImplementedASTM NetRID nominal behavior
NET0260,Table1,1ImplementedASTM NetRID nominal behavior
NET0260,Table1,1aImplementedASTM NetRID nominal behavior
NET0260,Table1,23ImplementedASTM NetRID nominal behavior
NET0260,Table1,24ImplementedASTM NetRID nominal behavior
NET0260,Table1,25ImplementedASTM NetRID nominal behavior
NET0260,Table1,26ImplementedASTM NetRID nominal behavior
NET0260,Table1,7ImplementedASTM NetRID nominal behavior
NET0260,Table1,9ImplementedASTM NetRID nominal behavior
NET0260-aImplementedASTM F3411-22a NetRID aggregate checks
NET0270ImplementedASTM NetRID nominal behavior
NET0290ImplementedASTM NetRID nominal behavior
NET0420ImplementedASTM F3411-22a NetRID aggregate checks
NET0430ImplementedASTM NetRID nominal behavior
NET0440ImplementedASTM F3411-22a NetRID aggregate checks
NET0470In progressASTM NetRID nominal behavior
NET0470,Table1,1ImplementedASTM NetRID nominal behavior
NET0470,Table1,10PlannedASTM NetRID nominal behavior
NET0470,Table1,11PlannedASTM NetRID nominal behavior
NET0470,Table1,14ImplementedASTM NetRID nominal behavior
NET0470,Table1,15ImplementedASTM NetRID nominal behavior
NET0470,Table1,19ImplementedASTM NetRID nominal behavior
NET0470,Table1,1aImplementedASTM NetRID nominal behavior
NET0470,Table1,20ImplementedASTM NetRID nominal behavior
NET0470,Table1,23ImplementedASTM NetRID nominal behavior
NET0470,Table1,24ImplementedASTM NetRID nominal behavior
NET0470,Table1,25ImplementedASTM NetRID nominal behavior
NET0470,Table1,26ImplementedASTM NetRID nominal behavior
NET0470,Table1,5ImplementedASTM NetRID nominal behavior
NET0470,Table1,7ImplementedASTM NetRID nominal behavior
NET0470,Table1,9ImplementedASTM NetRID nominal behavior
NET0480ImplementedASTM NetRID nominal behavior
NET0490ImplementedASTM NetRID nominal behavior
NET0500ImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
NET0610ImplementedASTM NetRID nominal behavior
NET0710ImplementedASTM NetRID nominal behavior
interuss
.automated_testing
.rid
.injection
DeleteTestSuccessImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
ExpectedBehaviorImplementedASTM NetRID nominal behavior
UpsertTestResultPlannedASTM NetRID nominal behavior
UpsertTestSuccessImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
interuss
.automated_testing
.rid
.observation
ObservationSuccessImplementedASTM NetRID nominal behavior
UniqueFlightsImplementedASTM NetRID nominal behavior
diff --git a/monitoring/uss_qualifier/suites/uspace/required_services.md b/monitoring/uss_qualifier/suites/uspace/required_services.md index e69de29bb2..e3ba2e4225 100644 --- a/monitoring/uss_qualifier/suites/uspace/required_services.md +++ b/monitoring/uss_qualifier/suites/uspace/required_services.md @@ -0,0 +1,355 @@ + +# U-space required services test suite +[`suites.uspace.required_services`](./required_services.yaml) + +## Actions + +1. Suite: [U-space flight authorisation](flight_auth.md) ([`suites.uspace.flight_auth`](flight_auth.yaml)) +2. Suite: [U-Space network identification](network_identification.md) ([`suites.uspace.network_identification`](network_identification.yaml)) + +## Checked requirements + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PackageRequirementStatusChecked in
astm
.f3411
.v19
NET0220ImplementedASTM F3411-22a NetRID aggregate checks
astm
.f3411
.v22a
DSS0030ImplementedASTM NetRID nominal behavior
NET0240PlannedASTM F3411-22a NetRID aggregate checks
NET0260ImplementedASTM NetRID nominal behavior
NET0260,Table1,1ImplementedASTM NetRID nominal behavior
NET0260,Table1,1aImplementedASTM NetRID nominal behavior
NET0260,Table1,23ImplementedASTM NetRID nominal behavior
NET0260,Table1,24ImplementedASTM NetRID nominal behavior
NET0260,Table1,25ImplementedASTM NetRID nominal behavior
NET0260,Table1,26ImplementedASTM NetRID nominal behavior
NET0260,Table1,7ImplementedASTM NetRID nominal behavior
NET0260,Table1,9ImplementedASTM NetRID nominal behavior
NET0260-aImplementedASTM F3411-22a NetRID aggregate checks
NET0270ImplementedASTM NetRID nominal behavior
NET0290ImplementedASTM NetRID nominal behavior
NET0420ImplementedASTM F3411-22a NetRID aggregate checks
NET0430ImplementedASTM NetRID nominal behavior
NET0440ImplementedASTM F3411-22a NetRID aggregate checks
NET0470In progressASTM NetRID nominal behavior
NET0470,Table1,1ImplementedASTM NetRID nominal behavior
NET0470,Table1,10PlannedASTM NetRID nominal behavior
NET0470,Table1,11PlannedASTM NetRID nominal behavior
NET0470,Table1,14ImplementedASTM NetRID nominal behavior
NET0470,Table1,15ImplementedASTM NetRID nominal behavior
NET0470,Table1,19ImplementedASTM NetRID nominal behavior
NET0470,Table1,1aImplementedASTM NetRID nominal behavior
NET0470,Table1,20ImplementedASTM NetRID nominal behavior
NET0470,Table1,23ImplementedASTM NetRID nominal behavior
NET0470,Table1,24ImplementedASTM NetRID nominal behavior
NET0470,Table1,25ImplementedASTM NetRID nominal behavior
NET0470,Table1,26ImplementedASTM NetRID nominal behavior
NET0470,Table1,5ImplementedASTM NetRID nominal behavior
NET0470,Table1,7ImplementedASTM NetRID nominal behavior
NET0470,Table1,9ImplementedASTM NetRID nominal behavior
NET0480ImplementedASTM NetRID nominal behavior
NET0490ImplementedASTM NetRID nominal behavior
NET0500ImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
NET0610ImplementedASTM NetRID nominal behavior
NET0710ImplementedASTM NetRID nominal behavior
astm
.f3548
.v21
DSS0005ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
GEN0310ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
GEN0500ImplementedValidation of operational intents
OPIN0015ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
OPIN0020ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
OPIN0025ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
OPIN0030ImplementedValidation of operational intents
OPIN0040ImplementedValidation of operational intents
SCD0015ImplementedNominal planning: conflict with higher priority
SCD0020ImplementedNominal planning: conflict with higher priority
SCD0025ImplementedNominal planning: conflict with higher priority
SCD0030ImplementedNominal planning: conflict with higher priority
SCD0035ImplementedNominal planning: not permitted conflict with equal priority
SCD0040ImplementedNominal planning: not permitted conflict with equal priority
SCD0045ImplementedNominal planning: not permitted conflict with equal priority
SCD0050ImplementedNominal planning: not permitted conflict with equal priority
USS0005ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
USS0105ImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
interuss
.automated_testing
.flight_planning
ClearAreaImplementedNominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
DeleteFlightSuccessImplementedFlight authorisation validation
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
ExpectedBehaviorImplementedFlight authorisation validation
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Validation of operational intents
interuss
.automated_testing
.rid
.injection
DeleteTestSuccessImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
ExpectedBehaviorImplementedASTM NetRID nominal behavior
UpsertTestResultPlannedASTM NetRID nominal behavior
UpsertTestSuccessImplementedASTM NetRID SP clients misbehavior handling
ASTM NetRID nominal behavior
interuss
.automated_testing
.rid
.observation
ObservationSuccessImplementedASTM NetRID nominal behavior
UniqueFlightsImplementedASTM NetRID nominal behavior
From 544f696d3e074436b99ce5ed86e7509f502e5096 Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Thu, 14 Sep 2023 22:10:21 +0200 Subject: [PATCH 27/37] Update participants --- .../scenarios/astm/netrid/display_data_evaluator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index b3a4b5fb74..ebe77ceab8 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -433,7 +433,6 @@ def _evaluate_normal_observation( details, participants=[ observer.participant_id, - mapping.injected_flight.uss_participant_id, ], ) From 9ec0318a449eaf1ee794de46a0ef6070360c0753 Mon Sep 17 00:00:00 2001 From: Michael Barroco Date: Thu, 14 Sep 2023 22:20:52 +0200 Subject: [PATCH 28/37] Prevent exception when number is not round --- monitoring/mock_uss/riddp/routes_observation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index 94ed617635..bcb6146174 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -62,7 +62,9 @@ def _make_flight_observation( p = flight.most_recent_position timestamp = p.time.replace( - microsecond=_limit_resolution(p.time.microsecond, pow(10, 5)) + microsecond=round( + _limit_resolution(p.time.microsecond, pow(10, 5)) + ) # Microsecond is very strict on the expected size and format, thus the round. ) current_state = observation_api.CurrentState( timestamp=timestamp.isoformat(), From 6cae1fd9075eb1ee6b535d9c3049046d0ee2b532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Misbach?= Date: Tue, 19 Sep 2023 13:09:28 +0200 Subject: [PATCH 29/37] updates --- interfaces/automated_testing | 2 +- interfaces/rid/v1 | 2 +- monitoring/monitorlib/fetch/rid.py | 8 +- monitoring/monitorlib/formatting.py | 2 +- .../suites/astm/netrid/f3411_22a.md | 78 ++++++++++++++++++- .../suites/uspace/network_identification.md | 27 ++++--- .../suites/uspace/required_services.md | 27 ++++--- 7 files changed, 116 insertions(+), 30 deletions(-) diff --git a/interfaces/automated_testing b/interfaces/automated_testing index 8c83e2735c..e5e5ff2a3f 160000 --- a/interfaces/automated_testing +++ b/interfaces/automated_testing @@ -1 +1 @@ -Subproject commit 8c83e2735c762f6fee8d6ca62ee1c1c0d479512c +Subproject commit e5e5ff2a3f1ae6a381402e9ce5f9efb37d3b9876 diff --git a/interfaces/rid/v1 b/interfaces/rid/v1 index 935f3b64cf..228b301be1 160000 --- a/interfaces/rid/v1 +++ b/interfaces/rid/v1 @@ -1 +1 @@ -Subproject commit 935f3b64cf252e82777a74037001e17420bc3803 +Subproject commit 228b301be1f4466df9b014f20b515665861a8e67 diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index 3fa045b07d..c220bf82f9 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -252,7 +252,7 @@ def operational_status(self) -> v22a.api.RIDOperationalStatus: ) @property - def track(self): + def track(self) -> float: if self.rid_version == RIDVersion.f3411_19: return self.v19_value.current_state.track elif self.rid_version == RIDVersion.f3411_22a: @@ -263,7 +263,7 @@ def track(self): ) @property - def speed(self): + def speed(self) -> float: if self.rid_version == RIDVersion.f3411_19: return self.v19_value.current_state.speed elif self.rid_version == RIDVersion.f3411_22a: @@ -402,7 +402,9 @@ def arbitrary_uas_id(self) -> Optional[str]: ) @property - def operator_location(self) -> v22a.api.OperatorLocation: + def operator_location( + self, + ) -> v22a.api.OperatorLocation: # TODO: split in position + altitude if self.rid_version == RIDVersion.f3411_19: return v22a.api.OperatorLocation(position=self.v19_value.operator_location) elif self.rid_version == RIDVersion.f3411_22a: diff --git a/monitoring/monitorlib/formatting.py b/monitoring/monitorlib/formatting.py index 6436a00e2e..c2a5704dc2 100644 --- a/monitoring/monitorlib/formatting.py +++ b/monitoring/monitorlib/formatting.py @@ -143,6 +143,6 @@ def make_datetime(t) -> datetime.datetime: raise ValueError("Could not convert {} to datetime".format(str(type(t)))) -def _limit_resolution(value: float, resolution: float): +def _limit_resolution(value: float, resolution: float) -> float: """Change resolution of a value""" return round(value / resolution) * resolution diff --git a/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md b/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md index a1c91bed81..4a09874d25 100644 --- a/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md +++ b/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md @@ -23,7 +23,7 @@ Checked in - astm
.f3411
.v22a
+ astm
.f3411
.v22a
A2-6-1,1a Implemented ASTM F3411-22a NetRID DSS interoperability @@ -280,6 +280,81 @@ NET0470 + Implemented + TODO + ASTM NetRID nominal behavior + + + NET0470,Table1,1 + Implemented + ASTM NetRID nominal behavior + + + NET0470,Table1,10 + TODO + ASTM NetRID nominal behavior + + + NET0470,Table1,11 + TODO + ASTM NetRID nominal behavior + + + NET0470,Table1,14 + Implemented + ASTM NetRID nominal behavior + + + NET0470,Table1,15 + Implemented + ASTM NetRID nominal behavior + + + NET0470,Table1,19 + Implemented + ASTM NetRID nominal behavior + + + NET0470,Table1,1a + Implemented + ASTM NetRID nominal behavior + + + NET0470,Table1,20 + Implemented + ASTM NetRID nominal behavior + + + NET0470,Table1,23 + Implemented + ASTM NetRID nominal behavior + + + NET0470,Table1,24 + Implemented + ASTM NetRID nominal behavior + + + NET0470,Table1,25 + Implemented + ASTM NetRID nominal behavior + + + NET0470,Table1,26 + Implemented + ASTM NetRID nominal behavior + + + NET0470,Table1,5 + Implemented + ASTM NetRID nominal behavior + + + NET0470,Table1,7 + Implemented + ASTM NetRID nominal behavior + + + NET0470,Table1,9 Implemented ASTM NetRID nominal behavior @@ -351,4 +426,3 @@ ASTM NetRID nominal behavior ->>>>>>> main diff --git a/monitoring/uss_qualifier/suites/uspace/network_identification.md b/monitoring/uss_qualifier/suites/uspace/network_identification.md index 6f1ecc2e57..24f6a7e9e4 100644 --- a/monitoring/uss_qualifier/suites/uspace/network_identification.md +++ b/monitoring/uss_qualifier/suites/uspace/network_identification.md @@ -16,7 +16,7 @@ Checked in - astm
.f3411
.v22a
+ astm
.f3411
.v22a
A2-6-1,1a Implemented ASTM F3411-22a NetRID DSS interoperability @@ -266,9 +266,14 @@ Implemented ASTM F3411-22a NetRID aggregate checks + + NET0450 + Implemented + ASTM NetRID nominal behavior + NET0470 - In progress + Implemented + TODO ASTM NetRID nominal behavior @@ -278,12 +283,12 @@ NET0470,Table1,10 - Planned + TODO ASTM NetRID nominal behavior NET0470,Table1,11 - Planned + TODO ASTM NetRID nominal behavior @@ -342,12 +347,7 @@ ASTM NetRID nominal behavior - NET0450 - Implemented - ASTM NetRID nominal behavior - - - NET0470 + NET0470,Table1,9 Implemented ASTM NetRID nominal behavior @@ -379,7 +379,12 @@ NET0710 Implemented - ASTM NetRID nominal behavior + ASTM NetRID DSS: Simple ISA
ASTM NetRID nominal behavior + + + NET0730 + Implemented + ASTM NetRID DSS: Simple ISA interuss
.automated_testing
.rid
.injection
diff --git a/monitoring/uss_qualifier/suites/uspace/required_services.md b/monitoring/uss_qualifier/suites/uspace/required_services.md index 9bb3e17988..b7a5f8c639 100644 --- a/monitoring/uss_qualifier/suites/uspace/required_services.md +++ b/monitoring/uss_qualifier/suites/uspace/required_services.md @@ -17,7 +17,7 @@ Checked in - astm
.f3411
.v22a
+ astm
.f3411
.v22a
A2-6-1,1a Implemented ASTM F3411-22a NetRID DSS interoperability @@ -267,9 +267,14 @@ Implemented ASTM F3411-22a NetRID aggregate checks + + NET0450 + Implemented + ASTM NetRID nominal behavior + NET0470 - In progress + Implemented + TODO ASTM NetRID nominal behavior @@ -279,12 +284,12 @@ NET0470,Table1,10 - Planned + TODO ASTM NetRID nominal behavior NET0470,Table1,11 - Planned + TODO ASTM NetRID nominal behavior @@ -343,12 +348,7 @@ ASTM NetRID nominal behavior - NET0450 - Implemented - ASTM NetRID nominal behavior - - - NET0470 + NET0470,Table1,9 Implemented ASTM NetRID nominal behavior @@ -380,7 +380,12 @@ NET0710 Implemented - ASTM NetRID nominal behavior + ASTM NetRID DSS: Simple ISA
ASTM NetRID nominal behavior + + + NET0730 + Implemented + ASTM NetRID DSS: Simple ISA astm
.f3548
.v21
From 69fa4399950bef4f1565b14895e796ac73d6c221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Misbach?= Date: Tue, 19 Sep 2023 18:21:45 +0200 Subject: [PATCH 30/37] various fixes --- .../mock_uss/riddp/routes_observation.py | 53 +++++----- monitoring/monitorlib/fetch/rid.py | 96 ++++++++++++++++--- monitoring/monitorlib/formatting.py | 2 +- monitoring/monitorlib/geo.py | 4 +- .../netrid/common_dictionary_evaluator.py | 86 +++++++++++------ .../common_dictionary_evaluator_test.py | 6 +- .../astm/netrid/v22a/nominal_behavior.md | 12 +-- requirements.txt | 2 +- 8 files changed, 180 insertions(+), 81 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index bcb6146174..7171472deb 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -23,7 +23,7 @@ from .behavior import DisplayProviderBehavior from .config import KEY_RID_VERSION from .database import db -from monitoring.monitorlib.formatting import _limit_resolution +from monitoring.monitorlib.formatting import limit_resolution def _make_flight_observation( @@ -63,18 +63,18 @@ def _make_flight_observation( p = flight.most_recent_position timestamp = p.time.replace( microsecond=round( - _limit_resolution(p.time.microsecond, pow(10, 5)) + limit_resolution(p.time.microsecond, pow(10, 5)) ) # Microsecond is very strict on the expected size and format, thus the round. ) current_state = observation_api.CurrentState( timestamp=timestamp.isoformat(), operational_status=flight.operational_status, - track=_limit_resolution(flight.track, MinTrackDirectionResolution), - speed=_limit_resolution(flight.speed, MaxSpeed), + track=limit_resolution(flight.track, MinTrackDirectionResolution), + speed=limit_resolution(flight.speed, MaxSpeed), ) h = p.get("height") if h: - h.distance = _limit_resolution(h.distance, MinHeightResolution) + h.distance = limit_resolution(h.distance, MinHeightResolution) return observation_api.Flight( id=flight.id, most_recent_position=observation_api.Position( @@ -191,26 +191,27 @@ def riddp_flight_details(flight_id: str) -> Tuple[str, int]: flight_details = fetch.flight_details( flight_info.flights_url, flight_id, True, rid_version, utm_client ) - if rid_version == RIDVersion.f3411_19: - # TODO: Implement details for F3411-19 - return flask.jsonify( - observation_api.GetDetailsResponse(operator=observation_api.Operator()) + details = flight_details.details + + result = observation_api.GetDetailsResponse( + operator=observation_api.Operator( + id=details.operator_id, + location=None, + altitude=observation_api.OperatorAltitude(), + ), + uas=observation_api.UAS( + id=details.arbitrary_uas_id, + ), + ) + if details.operator_location is not None: + result.operator.location = observation_api.LatLngPoint( + lat=details.operator_location.lat, + lng=details.operator_location.lng, ) - elif rid_version == RIDVersion.f3411_22a: - details = flight_details.details - result = observation_api.GetDetailsResponse( - operator=observation_api.Operator( - id=details.operator_id, - location=details.operator_location.position, - altitude=observation_api.OperatorAltitude( - altitude=details.operator_location.get("altitude"), - altitude_type=details.operator_location.get("altitude_type"), - ), - ), - uas=observation_api.UAS( - id=details.arbitrary_uas_id, - ), + if details.operator_altitude is not None: + result.operator.altitude.altitude = details.operator_altitude.value + if details.operator_altitude_type is not None: + result.operator.altitude.altitude_type = ( + observation_api.OperatorAltitudeAltitudeType(details.operator_altitude_type) ) - return flask.jsonify(result) - else: - return f"Support for RID version {rid_version} not yet implemented", 501 + return flask.jsonify(result) diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index c220bf82f9..5363a0d3c1 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -2,7 +2,7 @@ import datetime from typing import Dict, List, Optional, Any, Union -from implicitdict import ImplicitDict +from implicitdict import ImplicitDict, StringBasedDateTime import s2sphere from uas_standards.astm.f3411 import v19, v22a import uas_standards.astm.f3411.v19.api @@ -239,12 +239,14 @@ def recent_positions(self) -> List[Position]: ) @property - def operational_status(self) -> v22a.api.RIDOperationalStatus: + def operational_status(self) -> Optional[str]: if self.rid_version == RIDVersion.f3411_19: - return v22a.api.RIDOperationalStatus( - self.v19_value.current_state.operational_status - ) + if self.v19_value.current_state is None: + return None + return self.v19_value.current_state.operational_status elif self.rid_version == RIDVersion.f3411_22a: + if self.v22a_value.current_state is None: + return None return self.v22a_value.current_state.operational_status else: raise NotImplementedError( @@ -252,10 +254,14 @@ def operational_status(self) -> v22a.api.RIDOperationalStatus: ) @property - def track(self) -> float: + def track(self) -> Optional[float]: if self.rid_version == RIDVersion.f3411_19: + if self.v19_value.current_state is None: + return None return self.v19_value.current_state.track elif self.rid_version == RIDVersion.f3411_22a: + if self.v22a_value.current_state is None: + return None return self.v22a_value.current_state.track else: raise NotImplementedError( @@ -263,16 +269,35 @@ def track(self) -> float: ) @property - def speed(self) -> float: + def speed(self) -> Optional[float]: if self.rid_version == RIDVersion.f3411_19: + if self.v19_value.current_state is None: + return None return self.v19_value.current_state.speed elif self.rid_version == RIDVersion.f3411_22a: + if self.v22a_value.current_state is None: + return None return self.v22a_value.current_state.speed else: raise NotImplementedError( f"Cannot retrieve speed using RID version {self.rid_version}" ) + @property + def timestamp(self) -> Optional[StringBasedDateTime]: + if self.rid_version == RIDVersion.f3411_19: + if self.v19_value.current_state is None: + return None + return self.v19_value.current_state.timestamp + elif self.rid_version == RIDVersion.f3411_22a: + if self.v22a_value.current_state is None: + return None + return self.v22a_value.current_state.timestamp.value + else: + raise NotImplementedError( + f"Cannot retrieve speed using RID version {self.rid_version}" + ) + def errors(self) -> List[str]: try: rid_version = self.rid_version @@ -377,7 +402,7 @@ def arbitrary_uas_id(self) -> Optional[str]: """Returns a UAS id as a plain string without type hint. If multiple are provided: For v19, registration_number is returned if set, else it falls back to the serial_number. - For v20, the order of ASTM F3411-v19 Table 1 is used. + For v22a, the order of ASTM F3411-v22a Table 1 is used. If no match, it returns None. """ if self.rid_version == RIDVersion.f3411_19: @@ -404,14 +429,61 @@ def arbitrary_uas_id(self) -> Optional[str]: @property def operator_location( self, - ) -> v22a.api.OperatorLocation: # TODO: split in position + altitude + ) -> Optional[geo.LatLngPoint]: if self.rid_version == RIDVersion.f3411_19: - return v22a.api.OperatorLocation(position=self.v19_value.operator_location) + if self.v19_value.operator_location is None: + return None + return geo.LatLngPoint( + lat=self.v19_value.operator_location.lat, + lng=self.v19_value.operator_location.lng, + ) elif self.rid_version == RIDVersion.f3411_22a: - return self.v22a_value.operator_location + if self.v22a_value.operator_location is None: + return None + pos = self.v22a_value.operator_location.position + return geo.LatLngPoint(lat=pos.lat, lng=pos.lng) + else: + raise NotImplementedError( + f"Cannot retrieve operator_position using RID version {self.rid_version}" + ) + + @property + def operator_altitude( + self, + ) -> Optional[geo.Altitude]: + if self.rid_version == RIDVersion.f3411_19: + return None + elif self.rid_version == RIDVersion.f3411_22a: + if ( + self.v22a_value.operator_location is None + or self.v22a_value.operator_location.altitude is None + ): + return None + alt = self.v22a_value.operator_location.altitude + return geo.Altitude( + value=alt.value, reference=alt.reference, units=alt.units + ) + else: + raise NotImplementedError( + f"Cannot retrieve operator_altitude using RID version {self.rid_version}" + ) + + @property + def operator_altitude_type( + self, + ) -> Optional[str]: + if self.rid_version == RIDVersion.f3411_19: + return None + elif self.rid_version == RIDVersion.f3411_22a: + if ( + self.v22a_value.operator_location is None + or self.v22a_value.operator_location.altitude_type is None + ): + return None + return self.v22a_value.operator_location.altitude_type else: raise NotImplementedError( - f"Cannot retrieve operator_location using RID version {self.rid_version}" + f"Cannot retrieve operator_altitude_type using RID version {self.rid_version}" ) diff --git a/monitoring/monitorlib/formatting.py b/monitoring/monitorlib/formatting.py index c2a5704dc2..ca6663b661 100644 --- a/monitoring/monitorlib/formatting.py +++ b/monitoring/monitorlib/formatting.py @@ -143,6 +143,6 @@ def make_datetime(t) -> datetime.datetime: raise ValueError("Could not convert {} to datetime".format(str(type(t)))) -def _limit_resolution(value: float, resolution: float) -> float: +def limit_resolution(value: float, resolution: float) -> float: """Change resolution of a value""" return round(value / resolution) * resolution diff --git a/monitoring/monitorlib/geo.py b/monitoring/monitorlib/geo.py index ef002d6c60..e58c18fa74 100644 --- a/monitoring/monitorlib/geo.py +++ b/monitoring/monitorlib/geo.py @@ -157,14 +157,14 @@ def make_latlng_rect(area) -> s2sphere.LatLngRect: ) -def validate_lat(lat: str) -> float: +def validate_lat(lat: Union[str, float]) -> float: lat = float(lat) if lat < -90 or lat > 90: raise ValueError("Latitude must be in [-90, 90] range") return lat -def validate_lng(lng: str) -> float: +def validate_lng(lng: Union[str, float]) -> float: lng = float(lng) if lng < -180 or lng > 180: raise ValueError("Longitude must be in [-180, 180] range") diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index 7fc9a69c06..1f3fc8c7fb 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -1,8 +1,11 @@ import datetime +import math + from arrow import ParserError from implicitdict import StringBasedDateTime from typing import List, Optional import s2sphere +from uas_standards.astm.f3411.v22a.api import UASID from uas_standards.interuss.automated_testing.rid.v1.observation import ( GetDetailsResponse, @@ -24,15 +27,12 @@ MinTrackDirectionResolution, ) -from uas_standards.astm.f3411.v22a.constants import ( - MinHeightResolution, - MinPositionResolution, -) +from uas_standards.astm.f3411.v22a.constants import MinHeightResolution from monitoring.monitorlib.fetch.rid import ( FetchedFlights, FlightDetails, ) -from monitoring.monitorlib.formatting import _limit_resolution +from monitoring.monitorlib.formatting import limit_resolution from monitoring.monitorlib.geo import validate_lat, validate_lng, Altitude, LatLngPoint from monitoring.monitorlib.rid import RIDVersion from monitoring.uss_qualifier.common_data_definitions import Severity @@ -92,12 +92,11 @@ def evaluate_dp_flight( observed_flight: Flight, participants: List[str], ): - current_state = observed_flight.current_state - self._evaluate_speed(current_state.speed, participants) - self._evaluate_track(current_state.track, participants) - self._evaluate_timestamp(current_state.timestamp, participants) + self._evaluate_speed(observed_flight.speed, participants) + self._evaluate_track(observed_flight.track, participants) + self._evaluate_timestamp(observed_flight.timestamp, participants) self._evaluate_operational_status( - current_state.operational_status, participants + observed_flight.operational_status, participants ) self._evaluate_position(observed_flight.most_recent_position, participants) self._evaluate_height( @@ -133,7 +132,7 @@ def _chronological_positions(self, f: Flight) -> List[s2sphere.LatLng]: for p in sorted(f.recent_positions, key=lambda p: p.time) ] - def _sliding_triples( + def _sliding_triples( # TODO self, points: List[s2sphere.LatLng] ) -> List[List[s2sphere.LatLng]]: """ @@ -197,9 +196,9 @@ def evaluate_sp_details(self, details: FlightDetails, participants: List[str]): self._evaluate_uas_id(details.raw.get("uas_id"), participants) self._evaluate_operator_id(details.operator_id, participants) self._evaluate_operator_location( - details.operator_location.position, - details.operator_location.get("altitude"), - details.operator_location.get("altitude_type"), + details.operator_location, + details.operator_altitude, + details.operator_altitude_type, participants, ) @@ -226,9 +225,7 @@ def evaluate_dp_details( participants, ) - def _evaluate_uas_id( - self, value: Optional[v22a.api.UASID], participants: List[str] - ): + def _evaluate_uas_id(self, value: Optional[UASID], participants: List[str]): if self._rid_version == RIDVersion.f3411_22a: formats_keys = [ "serial_number", @@ -303,21 +300,29 @@ def _evaluate_arbitrary_uas_id(self, value: str, participants: List[str]): message=f"Unsupported version {self._rid_version}: skipping arbitrary uas id evaluation", ) - def _evaluate_timestamp(self, timestamp: str, participants: List[str]): + def _evaluate_timestamp( + self, timestamp: Optional[StringBasedDateTime], participants: List[str] + ): if self._rid_version == RIDVersion.f3411_22a: with self._test_scenario.check( "Timestamp consistency with Common Dictionary", participants ) as check: + if timestamp is None: + check.record_failed( + f"Timestamp not present", + details=f"The timestamp must be specified.", + severity=Severity.High, + ) + try: - t = StringBasedDateTime(timestamp) - if t.datetime.utcoffset().seconds != 0: + if timestamp.datetime.utcoffset().seconds != 0: check.record_failed( f"Timestamp must be relative to UTC: {timestamp}", severity=Severity.Medium, ) - ms = t.datetime.microsecond - ms_res = _limit_resolution(ms, pow(10, 5)) - if ms != ms_res: + us = timestamp.datetime.microsecond + us_res = limit_resolution(us, pow(10, 5)) + if not math.isclose(us, us_res): check.record_failed( f"Timestamp resolution is smaller than 1/10 second: {timestamp}", severity=Severity.Medium, @@ -352,11 +357,18 @@ def _evaluate_operator_id(self, value: Optional[str], participants: List[str]): message=f"Unsupported version {self._rid_version}: skipping operator id evaluation", ) - def _evaluate_speed(self, speed: float, participants: List[str]): + def _evaluate_speed(self, speed: Optional[float], participants: List[str]): if self._rid_version == RIDVersion.f3411_22a: with self._test_scenario.check( "Speed consistency with Common Dictionary", participants ) as check: + if speed is None: + check.record_failed( + f"Speed not present", + details=f"The speed must be specified.", + severity=Severity.High, + ) + if not (0 <= speed <= MaxSpeed or round(speed) == SpecialSpeed): check.record_failed( f"Invalid speed: {speed}", @@ -364,7 +376,7 @@ def _evaluate_speed(self, speed: float, participants: List[str]): severity=Severity.Medium, ) - if speed != _limit_resolution(speed, MinSpeedResolution): + if not math.isclose(speed, limit_resolution(speed, MinSpeedResolution)): check.record_failed( f"Invalid speed resolution: {speed}", details=f"the speed resolution shall not be less than 0.25 m/s", @@ -376,11 +388,18 @@ def _evaluate_speed(self, speed: float, participants: List[str]): message=f"Unsupported version {self._rid_version}: skipping speed evaluation", ) - def _evaluate_track(self, track: float, participants: List[str]): + def _evaluate_track(self, track: Optional[float], participants: List[str]): if self._rid_version == RIDVersion.f3411_22a: with self._test_scenario.check( "Track Direction consistency with Common Dictionary", participants ) as check: + if track is None: + check.record_failed( + f"Track direction not present", + details=f"The track direction must be specified.", + severity=Severity.High, + ) + if not ( MinTrackDirection <= track <= MaxTrackDirection or round(track) == SpecialTrackDirection @@ -391,10 +410,12 @@ def _evaluate_track(self, track: float, participants: List[str]): severity=Severity.Medium, ) - if track != _limit_resolution(track, MinTrackDirectionResolution): + if not math.isclose( + track, limit_resolution(track, MinTrackDirectionResolution) + ): check.record_failed( f"Invalid track direction resolution: {track}", - details=f"the track direction resolution shall not be less than 1 degree", + details=f"The track direction resolution shall not be less than 1 degree.", severity=Severity.Medium, ) else: @@ -438,8 +459,9 @@ def _evaluate_height(self, height: Optional[RIDHeight], participants: List[str]) with self._test_scenario.check( "Height consistency with Common Dictionary", participants ) as check: - if height.distance != _limit_resolution( - height.distance, MinHeightResolution + if not math.isclose( + height.distance, + limit_resolution(height.distance, MinHeightResolution), ): check.record_failed( f"Invalid height resolution: {height.distance}", @@ -520,7 +542,9 @@ def _evaluate_operator_location( details=f"Invalid Operator Altitude units: {alt.units}", severity=Severity.Medium, ) - if alt.value != _limit_resolution(alt.value, 1): + if not math.isclose( + alt.value, limit_resolution(alt.value, MinHeightResolution) + ): check.record_failed( "Operator Altitude must have a minimum resolution of 1 m.", details=f"Invalid Operator Altitude: {alt.value}", diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py index c451041bb3..e7481333bc 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py @@ -1,6 +1,8 @@ from datetime import datetime, timedelta, timezone import s2sphere from typing import List, Tuple, Optional + +from implicitdict import StringBasedDateTime from uas_standards.interuss.automated_testing.rid.v1.observation import ( OperatorAltitudeAltitudeType, RIDHeight, @@ -210,7 +212,7 @@ def step_under_test(self: UnitTestScenario): rid_version=RIDVersion.f3411_22a, ) - evaluator._evaluate_timestamp(value, []) + evaluator._evaluate_timestamp(StringBasedDateTime(value), []) unit_test_scenario = UnitTestScenario(step_under_test).execute_unit_test() assert unit_test_scenario.get_report().successful == outcome @@ -220,7 +222,7 @@ def test_timestamp(): _assert_timestamp("2023-09-13T04:43:00.1Z", True) # Ok _assert_timestamp("2023-09-13T04:43:00Z", True) # Ok _assert_timestamp("2023-09-13T04:43:00.501Z", False) # Wrong resolution - _assert_timestamp("2023-09-13T04:43:00.1EST", False) # Wrong timezone + _assert_timestamp("2023-09-13T04:43:00.1+07:00", False) # Wrong timezone def _assert_speed(value: float, outcome: bool): diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md index 491c919670..cb77964c16 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md @@ -202,7 +202,7 @@ This check validates that the display area of a cluster, measured and provided i #### UAS ID (Serial Number format) consistency with Common Dictionary check -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the UAS ID is in serial number format. (**[astm.f3411.v22a.NET0470,Table1,1a](../../../../requirements/astm/f3411/v22a.md)**) +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that if the UAS ID is in serial number format, its format is valid. (**[astm.f3411.v22a.NET0470,Table1,1a](../../../../requirements/astm/f3411/v22a.md)**) #### Timestamp consistency with Common Dictionary check @@ -223,15 +223,15 @@ TODO: If the resolution is greater than 7 number digits, this check will fail. #### Height consistency with Common Dictionary check -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Track Direction (**[astm.f3411.v22a.NET0470,Table1,14](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Height resolution is less than 1 meter, this check will fail. +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Height (**[astm.f3411.v22a.NET0470,Table1,14](../../../../requirements/astm/f3411/v22a.md)**), if present, is valid. If the observed Height resolution is less than 1 meter, this check will fail. #### Height Type consistency with Common Dictionary check -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Height Type (**[astm.f3411.v22a.NET0470,Table1,15](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Height Type indicates a value different than Takeoff Location or Ground Level, this check will fail. +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Height Type (**[astm.f3411.v22a.NET0470,Table1,15](../../../../requirements/astm/f3411/v22a.md)**), if present, is valid. If the observed Height Type indicates a value different than Takeoff Location or Ground Level, this check will fail. #### Track Direction consistency with Common Dictionary check -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Track Direction (**[astm.f3411.v22a.NET0470,Table1,19](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Track Direction is less than -359 and is greater than 359 and different than 361 this check will fail. If the Track Direction resolution is less than 1 degree, this check will fail. +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Track Direction (**[astm.f3411.v22a.NET0470,Table1,19](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Track Direction is less than -359 or is greater than 359, except for the special value 361, this check will fail. If the Track Direction resolution is less than 1 degree, this check will fail. #### Speed consistency with Common Dictionary check @@ -243,11 +243,11 @@ TODO: If the resolution is greater than 7 number digits, this check will fail. #### Operator Altitude consistency with Common Dictionary check -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that if the Operator Altitude is based on WGS-84 height above ellipsoid (HAE), is provided in meters and must have a minimum resolution of 1 m. (**[astm.f3411.v22a.NET0470,Table1,25](../../../../requirements/astm/f3411/v22a.md)**) +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that, if present, the Operator Altitude is based on WGS-84 height above ellipsoid (HAE), is provided in meters, and has a minimum resolution of 1 m. (**[astm.f3411.v22a.NET0470,Table1,25](../../../../requirements/astm/f3411/v22a.md)**) #### Operator Altitude Type consistency with Common Dictionary check -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that if the Operator Altitude Type is valid, if present. (**[astm.f3411.v22a.NET0470,Table1,26](../../../../requirements/astm/f3411/v22a.md)**) +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Operator Altitude Type is valid, if present. (**[astm.f3411.v22a.NET0470,Table1,26](../../../../requirements/astm/f3411/v22a.md)**) ## Cleanup diff --git a/requirements.txt b/requirements.txt index a4c773ff81..cee4d53f0b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,5 +39,5 @@ s2sphere==0.2.5 shapely==1.7.1 structlog==21.5.0 # deployment_manager termcolor==1.1.0 -uas_standards==2.0.0 +uas_standards==2.1.0 Werkzeug==2.0.3 # See https://github.com/interuss/dss/issues/753 From ba022a55f2b8762dcfdceab55847c4e6fa67ac72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Misbach?= Date: Tue, 19 Sep 2023 19:40:42 +0200 Subject: [PATCH 31/37] fixes --- monitoring/monitorlib/fetch/rid.py | 18 ++--- .../netrid/common_dictionary_evaluator.py | 65 ++++++++++++------- .../astm/netrid/display_data_evaluator.py | 42 ++++++------ .../astm/netrid/v22a/nominal_behavior.md | 8 +++ 4 files changed, 80 insertions(+), 53 deletions(-) diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index 5363a0d3c1..9bb55bfa44 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -431,14 +431,14 @@ def operator_location( self, ) -> Optional[geo.LatLngPoint]: if self.rid_version == RIDVersion.f3411_19: - if self.v19_value.operator_location is None: + if not self.v19_value.has_field_with_value("operator_location"): return None return geo.LatLngPoint( lat=self.v19_value.operator_location.lat, lng=self.v19_value.operator_location.lng, ) elif self.rid_version == RIDVersion.f3411_22a: - if self.v22a_value.operator_location is None: + if not self.v22a_value.has_field_with_value("operator_location"): return None pos = self.v22a_value.operator_location.position return geo.LatLngPoint(lat=pos.lat, lng=pos.lng) @@ -454,10 +454,9 @@ def operator_altitude( if self.rid_version == RIDVersion.f3411_19: return None elif self.rid_version == RIDVersion.f3411_22a: - if ( - self.v22a_value.operator_location is None - or self.v22a_value.operator_location.altitude is None - ): + if not self.v22a_value.has_field_with_value( + "operator_location" + ) or not self.v22a_value.operator_location.has_field_with_value("altitude"): return None alt = self.v22a_value.operator_location.altitude return geo.Altitude( @@ -475,9 +474,10 @@ def operator_altitude_type( if self.rid_version == RIDVersion.f3411_19: return None elif self.rid_version == RIDVersion.f3411_22a: - if ( - self.v22a_value.operator_location is None - or self.v22a_value.operator_location.altitude_type is None + if not self.v22a_value.has_field_with_value( + "operator_location" + ) or not self.v22a_value.operator_location.has_field_with_value( + "altitude_type" ): return None return self.v22a_value.operator_location.altitude_type diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index 1f3fc8c7fb..7c009e1793 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -7,13 +7,17 @@ import s2sphere from uas_standards.astm.f3411.v22a.api import UASID -from uas_standards.interuss.automated_testing.rid.v1.observation import ( - GetDetailsResponse, - OperatorAltitudeAltitudeType, - RIDHeight, - RIDHeightReference, +from uas_standards.interuss.automated_testing.rid.v1 import ( + observation as observation_api, ) +# from uas_standards.interuss.automated_testing.rid.v1.observation import ( +# GetDetailsResponse, +# OperatorAltitudeAltitudeType, +# RIDHeight, +# RIDHeightReference, +# ) + from uas_standards.ansi_cta_2063_a import SerialNumber from uas_standards.astm.f3411 import v22a @@ -89,14 +93,22 @@ def evaluate_sp_flights( def evaluate_dp_flight( self, - observed_flight: Flight, + observed_flight: observation_api.Flight, participants: List[str], ): - self._evaluate_speed(observed_flight.speed, participants) - self._evaluate_track(observed_flight.track, participants) - self._evaluate_timestamp(observed_flight.timestamp, participants) + with self._test_scenario.check("Current state present", participants) as check: + if not observed_flight.has_field_with_value("current_state"): + check.record_failed( + f"Current state for flight {observed_flight.id}", + details=f"The current state must be specified.", + severity=Severity.High, + ) + + self._evaluate_speed(observed_flight.current_state.speed, participants) + self._evaluate_track(observed_flight.current_state.track, participants) + self._evaluate_timestamp(observed_flight.current_state.timestamp, participants) self._evaluate_operational_status( - observed_flight.operational_status, participants + observed_flight.current_state.operational_status, participants ) self._evaluate_position(observed_flight.most_recent_position, participants) self._evaluate_height( @@ -132,7 +144,7 @@ def _chronological_positions(self, f: Flight) -> List[s2sphere.LatLng]: for p in sorted(f.recent_positions, key=lambda p: p.time) ] - def _sliding_triples( # TODO + def _sliding_triples( self, points: List[s2sphere.LatLng] ) -> List[List[s2sphere.LatLng]]: """ @@ -203,7 +215,9 @@ def evaluate_sp_details(self, details: FlightDetails, participants: List[str]): ) def evaluate_dp_details( - self, observed_details: Optional[GetDetailsResponse], participants: List[str] + self, + observed_details: Optional[observation_api.GetDetailsResponse], + participants: List[str], ): if not observed_details: return @@ -300,9 +314,7 @@ def _evaluate_arbitrary_uas_id(self, value: str, participants: List[str]): message=f"Unsupported version {self._rid_version}: skipping arbitrary uas id evaluation", ) - def _evaluate_timestamp( - self, timestamp: Optional[StringBasedDateTime], participants: List[str] - ): + def _evaluate_timestamp(self, timestamp: Optional[str], participants: List[str]): if self._rid_version == RIDVersion.f3411_22a: with self._test_scenario.check( "Timestamp consistency with Common Dictionary", participants @@ -315,16 +327,17 @@ def _evaluate_timestamp( ) try: - if timestamp.datetime.utcoffset().seconds != 0: + t = StringBasedDateTime(timestamp) + if t.datetime.utcoffset().seconds != 0: check.record_failed( - f"Timestamp must be relative to UTC: {timestamp}", + f"Timestamp must be relative to UTC: {t}", severity=Severity.Medium, ) - us = timestamp.datetime.microsecond + us = t.datetime.microsecond us_res = limit_resolution(us, pow(10, 5)) if not math.isclose(us, us_res): check.record_failed( - f"Timestamp resolution is smaller than 1/10 second: {timestamp}", + f"Timestamp resolution is smaller than 1/10 second: {t}", severity=Severity.Medium, ) except ParserError as e: @@ -453,7 +466,9 @@ def _evaluate_position(self, position: Position, participants: List[str]): message=f"Unsupported version {self._rid_version}: skipping position evaluation", ) - def _evaluate_height(self, height: Optional[RIDHeight], participants: List[str]): + def _evaluate_height( + self, height: Optional[observation_api.RIDHeight], participants: List[str] + ): if self._rid_version == RIDVersion.f3411_22a: if height: with self._test_scenario.check( @@ -472,12 +487,14 @@ def _evaluate_height(self, height: Optional[RIDHeight], participants: List[str]) "Height Type consistency with Common Dictionary", participants ) as check: if ( - height.reference != RIDHeightReference.TakeoffLocation - and height.reference != RIDHeightReference.GroundLevel + height.reference + != observation_api.RIDHeightReference.TakeoffLocation + and height.reference + != observation_api.RIDHeightReference.GroundLevel ): check.record_failed( f"Invalid height type: {height.reference}", - details=f"The height type reference shall be either {RIDHeightReference.TakeoffLocation} or {RIDHeightReference.GroundLevel}", + details=f"The height type reference shall be either {observation_api.RIDHeightReference.TakeoffLocation} or {observation_api.RIDHeightReference.GroundLevel}", severity=Severity.Medium, ) else: @@ -490,7 +507,7 @@ def _evaluate_operator_location( self, position: Optional[LatLngPoint], altitude: Optional[Altitude], - altitude_type: Optional[OperatorAltitudeAltitudeType], + altitude_type: Optional[observation_api.OperatorAltitudeAltitudeType], participants: List[str], ): if self._rid_version == RIDVersion.f3411_22a: diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index 93c20f7047..f73785b71e 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -265,16 +265,6 @@ def evaluate_system_instantaneously( query, verified_sps, ) - # We also issue queries to the flight details endpoint in order to collect - # performance statistics, which are computed and checked at a later stage. - if query.status_code == 200: - # If there are multiple flights, we only issue a single details query for the first returned one, - # as we don't want to slow down the test we are piggy-backing on. - if len(observation.flights) > 0: - (_, detailQuery) = observer.observe_flight_details( - observation.flights[0].id, self._rid_version - ) - self._test_scenario.record_query(detailQuery) # TODO: If bounding rect is smaller than cluster threshold, expand slightly above cluster threshold and re-observe # TODO: If bounding rect is smaller than area-too-large threshold, expand slightly above area-too-large threshold and re-observe @@ -433,17 +423,29 @@ def _evaluate_normal_observation( ), ) - details, query = observer.observe_flight_details( - mapping.observed_flight.id, self._rid_version - ) - self._test_scenario.record_query(query) + with self._test_scenario.check( + "Successful details observation", + [mapping.injected_flight.uss_participant_id], + ) as check: + details, query = observer.observe_flight_details( + mapping.observed_flight.id, self._rid_version + ) + self._test_scenario.record_query(query) - self._common_dictionary_evaluator.evaluate_dp_details( - details, - participants=[ - observer.participant_id, - ], - ) + if query.status_code != 200: + check.record_failed( + summary=f"Observation of details failed for {mapping.observed_flight.id}", + details=f"When queried for details of observation (ID {mapping.observed_flight.id}), {observer.participant_id} returned code {query.status_code}", + severity=Severity.Medium, + query_timestamps=[query.request.timestamp], + ) + else: + self._common_dictionary_evaluator.evaluate_dp_details( + details, + participants=[ + observer.participant_id, + ], + ) def _evaluate_flight_presence( self, diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md index ffe2ee27ba..c64bba5c70 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md @@ -196,6 +196,14 @@ Taking into account the propagation time of the injected flights, if the total n For a display area with a diagonal greather than *NetDetailsMaxDisplayAreaDiagonal* and less than *NetMaxDisplayAreaDiagonal*, **[astm.f3411.v22a.NET0480](../../../../requirements/astm/f3411/v22a.md)** requires that a Display provider shall cluster UAs in close proximity to each other using a circular or polygonal area covering no less than *NetMinClusterSize* percent of the display area size. This check validates that the display area of a cluster, measured and provided in square meters by the test harness, is no less than *NetMinClusterSize* percent of the display area. +#### Successful details observation check + +Per **[interuss.automated_testing.rid.observation.ObservationSuccess](../../../../requirements/interuss/automated_testing/rid/observation.md)**, the call for flight details is expected to succeed since a valid ID was provided by uss_qualifier. + +#### Current state present check + +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the current state is present. If it is not, this check will fail. + #### UAS ID presence in flight details check **[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the UAS ID is present in the information sent by the Display Provider. (**[astm.f3411.v22a.NET0470,Table1,1](../../../../requirements/astm/f3411/v22a.md)**) From 2277e128071b2ceb47bf30402d49fb67d7608224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Misbach?= Date: Tue, 19 Sep 2023 19:52:27 +0200 Subject: [PATCH 32/37] align --- monitoring/monitorlib/fetch/rid.py | 32 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/monitoring/monitorlib/fetch/rid.py b/monitoring/monitorlib/fetch/rid.py index 9bb55bfa44..3f52335bfc 100644 --- a/monitoring/monitorlib/fetch/rid.py +++ b/monitoring/monitorlib/fetch/rid.py @@ -241,11 +241,19 @@ def recent_positions(self) -> List[Position]: @property def operational_status(self) -> Optional[str]: if self.rid_version == RIDVersion.f3411_19: - if self.v19_value.current_state is None: + if not self.v19_value.has_field_with_value( + "current_state" + ) or not self.v19_value.current_state.has_field_with_value( + "operational_status" + ): return None return self.v19_value.current_state.operational_status elif self.rid_version == RIDVersion.f3411_22a: - if self.v22a_value.current_state is None: + if not self.v22a_value.has_field_with_value( + "current_state" + ) or not self.v22a_value.current_state.has_field_with_value( + "operational_status" + ): return None return self.v22a_value.current_state.operational_status else: @@ -256,11 +264,15 @@ def operational_status(self) -> Optional[str]: @property def track(self) -> Optional[float]: if self.rid_version == RIDVersion.f3411_19: - if self.v19_value.current_state is None: + if not self.v19_value.has_field_with_value( + "current_state" + ) or not self.v19_value.current_state.has_field_with_value("track"): return None return self.v19_value.current_state.track elif self.rid_version == RIDVersion.f3411_22a: - if self.v22a_value.current_state is None: + if not self.v22a_value.has_field_with_value( + "current_state" + ) or not self.v22a_value.current_state.has_field_with_value("track"): return None return self.v22a_value.current_state.track else: @@ -271,11 +283,15 @@ def track(self) -> Optional[float]: @property def speed(self) -> Optional[float]: if self.rid_version == RIDVersion.f3411_19: - if self.v19_value.current_state is None: + if not self.v19_value.has_field_with_value( + "current_state" + ) or not self.v19_value.current_state.has_field_with_value("speed"): return None return self.v19_value.current_state.speed elif self.rid_version == RIDVersion.f3411_22a: - if self.v22a_value.current_state is None: + if not self.v22a_value.has_field_with_value( + "current_state" + ) or not self.v22a_value.current_state.has_field_with_value("speed"): return None return self.v22a_value.current_state.speed else: @@ -286,11 +302,11 @@ def speed(self) -> Optional[float]: @property def timestamp(self) -> Optional[StringBasedDateTime]: if self.rid_version == RIDVersion.f3411_19: - if self.v19_value.current_state is None: + if not self.v19_value.has_field_with_value("current_state"): return None return self.v19_value.current_state.timestamp elif self.rid_version == RIDVersion.f3411_22a: - if self.v22a_value.current_state is None: + if not self.v22a_value.has_field_with_value("current_state"): return None return self.v22a_value.current_state.timestamp.value else: From d5afadc413bdeab0f25a998b51600fb7befb70c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Misbach?= Date: Wed, 20 Sep 2023 14:55:25 +0200 Subject: [PATCH 33/37] fixes --- .../scenarios/astm/netrid/display_data_evaluator.py | 13 +++++++++++++ .../scenarios/astm/netrid/v19/nominal_behavior.md | 8 ++++++++ .../scenarios/astm/netrid/v22a/nominal_behavior.md | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index f73785b71e..a0556956e2 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -218,6 +218,11 @@ def __init__( raise ValueError( f"Cannot evaluate a system using RID version {rid_version} with a DSS using RID version {dss.rid_version}" ) + self._retrieved_flight_details: Set[ + str + ] = ( + set() + ) # Contains the observed IDs of the flights whose details were retrieved. def evaluate_system_instantaneously( self, @@ -385,6 +390,7 @@ def _evaluate_normal_observation( self._common_dictionary_evaluator.evaluate_dp_flight( mapping.observed_flight, [observer.participant_id] ) + # Check that flights using telemetry are not using extrapolated position data for mapping in mapping_by_injection_id.values(): injected_telemetry = mapping.injected_flight.flight.telemetry[ @@ -423,10 +429,16 @@ def _evaluate_normal_observation( ), ) + # Check details of flights (once per flight) + for mapping in mapping_by_injection_id.values(): with self._test_scenario.check( "Successful details observation", [mapping.injected_flight.uss_participant_id], ) as check: + # query for flight details only once per flight + if mapping.observed_flight.id in self._retrieved_flight_details: + continue + details, query = observer.observe_flight_details( mapping.observed_flight.id, self._rid_version ) @@ -440,6 +452,7 @@ def _evaluate_normal_observation( query_timestamps=[query.request.timestamp], ) else: + self._retrieved_flight_details.add(mapping.observed_flight.id) self._common_dictionary_evaluator.evaluate_dp_details( details, participants=[ diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/nominal_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/nominal_behavior.md index a28ac4f879..a71c94ea9e 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/nominal_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/nominal_behavior.md @@ -168,6 +168,14 @@ Taking into account the propagation time of the injected flights, if the total n For a display area with a diagonal greather than *NetDetailsMaxDisplayAreaDiagonal* and less than *NetMaxDisplayAreaDiagonal*, **[astm.f3411.v19.NET0480](../../../../requirements/astm/f3411/v19.md)** requires that a Display provider shall cluster UAs in close proximity to each other using a circular or polygonal area covering no less than *NetMinClusterSize* percent of the display area size. This check validates that the display area of a cluster, measured and provided in square meters by the test harness, is no less than *NetMinClusterSize* percent of the display area. +#### Successful details observation check + +Per **[interuss.automated_testing.rid.observation.ObservationSuccess](../../../../requirements/interuss/automated_testing/rid/observation.md)**, the call for flight details is expected to succeed since a valid ID was provided by uss_qualifier. + +#### Current state present check + +**[astm.f3411.v19.NET0470](../../../../requirements/astm/f3411/v19.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the current state is present. If it is not, this check will fail. + ## Cleanup The cleanup phase of this test scenario attempts to remove injected data from all SPs. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md index c64bba5c70..bf4bea06bf 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md @@ -198,7 +198,7 @@ This check validates that the display area of a cluster, measured and provided i #### Successful details observation check -Per **[interuss.automated_testing.rid.observation.ObservationSuccess](../../../../requirements/interuss/automated_testing/rid/observation.md)**, the call for flight details is expected to succeed since a valid ID was provided by uss_qualifier. +Per **[interuss.automated_testing.rid.observation.ObservationSuccess](../../../../requirements/interuss/automated_testing/rid/observation.md)**, the call for flight details is expected to succeed since a valid ID was provided by uss_qualifier. #### Current state present check From f6f08eabc5a15d41d7d46eb6f20f0bf279909703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Misbach?= Date: Wed, 20 Sep 2023 14:57:14 +0200 Subject: [PATCH 34/37] cleanup --- .../scenarios/astm/netrid/common_dictionary_evaluator.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index 7c009e1793..dd41d036f9 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -11,13 +11,6 @@ observation as observation_api, ) -# from uas_standards.interuss.automated_testing.rid.v1.observation import ( -# GetDetailsResponse, -# OperatorAltitudeAltitudeType, -# RIDHeight, -# RIDHeightReference, -# ) - from uas_standards.ansi_cta_2063_a import SerialNumber from uas_standards.astm.f3411 import v22a From f3064cd4b7efa2c1e4dd413b8be2ad07fc576a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Misbach?= Date: Wed, 20 Sep 2023 15:32:45 +0200 Subject: [PATCH 35/37] remove resolution limit for RID DP --- monitoring/mock_uss/riddp/routes_observation.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index 7171472deb..9858ec929a 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -61,13 +61,8 @@ def _make_flight_observation( paths.append(current_path) p = flight.most_recent_position - timestamp = p.time.replace( - microsecond=round( - limit_resolution(p.time.microsecond, pow(10, 5)) - ) # Microsecond is very strict on the expected size and format, thus the round. - ) current_state = observation_api.CurrentState( - timestamp=timestamp.isoformat(), + timestamp=p.time.isoformat(), operational_status=flight.operational_status, track=limit_resolution(flight.track, MinTrackDirectionResolution), speed=limit_resolution(flight.speed, MaxSpeed), From 43f8319caf9023d7460c04d3f0f5639ec6077597 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Misbach?= Date: Wed, 20 Sep 2023 16:06:17 +0200 Subject: [PATCH 36/37] remove resolution checks --- .../mock_uss/riddp/routes_observation.py | 6 +-- .../netrid/common_dictionary_evaluator.py | 49 +------------------ .../astm/netrid/v22a/nominal_behavior.md | 14 ++---- .../suites/astm/netrid/f3411_22a.md | 7 +-- .../suites/uspace/network_identification.md | 7 +-- .../suites/uspace/required_services.md | 7 +-- 6 files changed, 12 insertions(+), 78 deletions(-) diff --git a/monitoring/mock_uss/riddp/routes_observation.py b/monitoring/mock_uss/riddp/routes_observation.py index 9858ec929a..ec2ef5a840 100644 --- a/monitoring/mock_uss/riddp/routes_observation.py +++ b/monitoring/mock_uss/riddp/routes_observation.py @@ -6,13 +6,13 @@ from uas_standards.astm.f3411.v19.api import ErrorResponse from uas_standards.astm.f3411.v19.constants import Scope from uas_standards.astm.f3411.v22a.constants import ( - MaxSpeed, MinHeightResolution, MinTrackDirectionResolution, + MinSpeedResolution, ) from monitoring.monitorlib import geo from monitoring.monitorlib.fetch import rid as fetch -from monitoring.monitorlib.fetch.rid import Flight, FetchedISAs, Position +from monitoring.monitorlib.fetch.rid import Flight, FetchedISAs from monitoring.monitorlib.rid import RIDVersion from uas_standards.interuss.automated_testing.rid.v1 import ( observation as observation_api, @@ -65,7 +65,7 @@ def _make_flight_observation( timestamp=p.time.isoformat(), operational_status=flight.operational_status, track=limit_resolution(flight.track, MinTrackDirectionResolution), - speed=limit_resolution(flight.speed, MaxSpeed), + speed=limit_resolution(flight.speed, MinSpeedResolution), ) h = p.get("height") if h: diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index dd41d036f9..d5a2eab313 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -17,19 +17,15 @@ from uas_standards.astm.f3411.v22a.constants import ( SpecialSpeed, MaxSpeed, - MinSpeedResolution, SpecialTrackDirection, MinTrackDirection, MaxTrackDirection, - MinTrackDirectionResolution, ) -from uas_standards.astm.f3411.v22a.constants import MinHeightResolution from monitoring.monitorlib.fetch.rid import ( FetchedFlights, FlightDetails, ) -from monitoring.monitorlib.formatting import limit_resolution from monitoring.monitorlib.geo import validate_lat, validate_lng, Altitude, LatLngPoint from monitoring.monitorlib.rid import RIDVersion from monitoring.uss_qualifier.common_data_definitions import Severity @@ -326,13 +322,6 @@ def _evaluate_timestamp(self, timestamp: Optional[str], participants: List[str]) f"Timestamp must be relative to UTC: {t}", severity=Severity.Medium, ) - us = t.datetime.microsecond - us_res = limit_resolution(us, pow(10, 5)) - if not math.isclose(us, us_res): - check.record_failed( - f"Timestamp resolution is smaller than 1/10 second: {t}", - severity=Severity.Medium, - ) except ParserError as e: check.record_failed( f"Unable to parse timestamp: {timestamp}", @@ -375,19 +364,12 @@ def _evaluate_speed(self, speed: Optional[float], participants: List[str]): severity=Severity.High, ) - if not (0 <= speed <= MaxSpeed or round(speed) == SpecialSpeed): + if not (0 <= speed <= MaxSpeed or math.isclose(speed, SpecialSpeed)): check.record_failed( f"Invalid speed: {speed}", details=f"The speed shall be greater than 0 and less than {MaxSpeed}. The Special Value {SpecialSpeed} is allowed.", severity=Severity.Medium, ) - - if not math.isclose(speed, limit_resolution(speed, MinSpeedResolution)): - check.record_failed( - f"Invalid speed resolution: {speed}", - details=f"the speed resolution shall not be less than 0.25 m/s", - severity=Severity.Medium, - ) else: self._test_scenario.record_note( key="skip_reason", @@ -415,15 +397,6 @@ def _evaluate_track(self, track: Optional[float], participants: List[str]): details=f"The track direction shall be greater than -360 and less than {MaxSpeed}. The Special Value {SpecialSpeed} is allowed.", severity=Severity.Medium, ) - - if not math.isclose( - track, limit_resolution(track, MinTrackDirectionResolution) - ): - check.record_failed( - f"Invalid track direction resolution: {track}", - details=f"The track direction resolution shall not be less than 1 degree.", - severity=Severity.Medium, - ) else: self._test_scenario.record_note( key="skip_reason", @@ -464,18 +437,6 @@ def _evaluate_height( ): if self._rid_version == RIDVersion.f3411_22a: if height: - with self._test_scenario.check( - "Height consistency with Common Dictionary", participants - ) as check: - if not math.isclose( - height.distance, - limit_resolution(height.distance, MinHeightResolution), - ): - check.record_failed( - f"Invalid height resolution: {height.distance}", - details=f"the height resolution shall not be less than 1 meter", - severity=Severity.Medium, - ) with self._test_scenario.check( "Height Type consistency with Common Dictionary", participants ) as check: @@ -552,14 +513,6 @@ def _evaluate_operator_location( details=f"Invalid Operator Altitude units: {alt.units}", severity=Severity.Medium, ) - if not math.isclose( - alt.value, limit_resolution(alt.value, MinHeightResolution) - ): - check.record_failed( - "Operator Altitude must have a minimum resolution of 1 m.", - details=f"Invalid Operator Altitude: {alt.value}", - severity=Severity.Medium, - ) alt_type = altitude_type if alt_type: diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md index bf4bea06bf..faec11994b 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/nominal_behavior.md @@ -112,7 +112,7 @@ The timestamps of the injected telemetry usually start in the future. If a flig #### Operator Altitude consistency with Common Dictionary check -**[astm.f3411.v22a.NET0260](../../../../requirements/astm/f3411/v22a.md)** requires that relevant Remote ID data, consistent with the common data dictionary, be reported by the Service Provider. This check validates that if the Operator Altitude is based on WGS-84 height above ellipsoid (HAE), is provided in meters and must have a minimum resolution of 1 m. (**[astm.f3411.v22a.NET0260,Table1,25](../../../../requirements/astm/f3411/v22a.md)**) +**[astm.f3411.v22a.NET0260](../../../../requirements/astm/f3411/v22a.md)** requires that relevant Remote ID data, consistent with the common data dictionary, be reported by the Service Provider. This check validates that if the Operator Altitude is based on WGS-84 height above ellipsoid (HAE) and is provided in meters. (**[astm.f3411.v22a.NET0260,Table1,25](../../../../requirements/astm/f3411/v22a.md)**) #### Operator Altitude Type consistency with Common Dictionary check @@ -214,7 +214,7 @@ Per **[interuss.automated_testing.rid.observation.ObservationSuccess](../../../. #### Timestamp consistency with Common Dictionary check -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that timestamps are expressed with a minimum resolution of one tenth of a second and relative to UTC. (**[astm.f3411.v22a.NET0470,Table1,5](../../../../requirements/astm/f3411/v22a.md)**) +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that timestamps are relative to UTC. (**[astm.f3411.v22a.NET0470,Table1,5](../../../../requirements/astm/f3411/v22a.md)**) #### Operational Status consistency with Common Dictionary check @@ -229,21 +229,17 @@ Per **[interuss.automated_testing.rid.observation.ObservationSuccess](../../../. **[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Current Position provided is valid. (**[astm.f3411.v22a.NET0470,Table1,10](../../../../requirements/astm/f3411/v22a.md)** and **[astm.f3411.v22a.NET0470,Table1,11](../../../../requirements/astm/f3411/v22a.md)**). If the observed Current Position do not contain valid latitude and longitude, this check will fail. TODO: If the resolution is greater than 7 number digits, this check will fail. -#### Height consistency with Common Dictionary check - -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Height (**[astm.f3411.v22a.NET0470,Table1,14](../../../../requirements/astm/f3411/v22a.md)**), if present, is valid. If the observed Height resolution is less than 1 meter, this check will fail. - #### Height Type consistency with Common Dictionary check **[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Height Type (**[astm.f3411.v22a.NET0470,Table1,15](../../../../requirements/astm/f3411/v22a.md)**), if present, is valid. If the observed Height Type indicates a value different than Takeoff Location or Ground Level, this check will fail. #### Track Direction consistency with Common Dictionary check -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Track Direction (**[astm.f3411.v22a.NET0470,Table1,19](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Track Direction is less than -359 or is greater than 359, except for the special value 361, this check will fail. If the Track Direction resolution is less than 1 degree, this check will fail. +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Track Direction (**[astm.f3411.v22a.NET0470,Table1,19](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Track Direction is less than -359 or is greater than 359, except for the special value 361, this check will fail. #### Speed consistency with Common Dictionary check -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Speed (**[astm.f3411.v22a.NET0470,Table1,20](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Speed is negative or greater than 254.25 or equals 255, this check will fail. If the Speed resolution is less than 0.25 m/s, this check will fail. +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that the Speed (**[astm.f3411.v22a.NET0470,Table1,20](../../../../requirements/astm/f3411/v22a.md)**) is valid. If the observed Speed is negative or greater than 254.25, except for the special value 255, this check will fail. #### Operator Location consistency with Common Dictionary check @@ -251,7 +247,7 @@ TODO: If the resolution is greater than 7 number digits, this check will fail. #### Operator Altitude consistency with Common Dictionary check -**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that, if present, the Operator Altitude is based on WGS-84 height above ellipsoid (HAE), is provided in meters, and has a minimum resolution of 1 m. (**[astm.f3411.v22a.NET0470,Table1,25](../../../../requirements/astm/f3411/v22a.md)**) +**[astm.f3411.v22a.NET0470](../../../../requirements/astm/f3411/v22a.md)** requires that Net-RID Display Provider shall provide access to required and optional fields to Remote ID Display Applications according to the Common Dictionary. This check validates that, if present, the Operator Altitude is based on WGS-84 height above ellipsoid (HAE) and is provided in meters. (**[astm.f3411.v22a.NET0470,Table1,25](../../../../requirements/astm/f3411/v22a.md)**) #### Operator Altitude Type consistency with Common Dictionary check diff --git a/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md b/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md index 8af08863a8..4958f65391 100644 --- a/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md +++ b/monitoring/uss_qualifier/suites/astm/netrid/f3411_22a.md @@ -23,7 +23,7 @@ Checked in - astm
.f3411
.v22a
+ astm
.f3411
.v22a
A2-6-1,1a Implemented ASTM F3411-22a NetRID DSS interoperability @@ -303,11 +303,6 @@ TODO ASTM NetRID nominal behavior - - NET0470,Table1,14 - Implemented - ASTM NetRID nominal behavior - NET0470,Table1,15 Implemented diff --git a/monitoring/uss_qualifier/suites/uspace/network_identification.md b/monitoring/uss_qualifier/suites/uspace/network_identification.md index 305e6c193b..3f5d846080 100644 --- a/monitoring/uss_qualifier/suites/uspace/network_identification.md +++ b/monitoring/uss_qualifier/suites/uspace/network_identification.md @@ -16,7 +16,7 @@ Checked in - astm
.f3411
.v22a
+ astm
.f3411
.v22a
A2-6-1,1a Implemented ASTM F3411-22a NetRID DSS interoperability @@ -296,11 +296,6 @@ TODO ASTM NetRID nominal behavior - - NET0470,Table1,14 - Implemented - ASTM NetRID nominal behavior - NET0470,Table1,15 Implemented diff --git a/monitoring/uss_qualifier/suites/uspace/required_services.md b/monitoring/uss_qualifier/suites/uspace/required_services.md index a3412b4e2f..765f47ff7f 100644 --- a/monitoring/uss_qualifier/suites/uspace/required_services.md +++ b/monitoring/uss_qualifier/suites/uspace/required_services.md @@ -17,7 +17,7 @@ Checked in - astm
.f3411
.v22a
+ astm
.f3411
.v22a
A2-6-1,1a Implemented ASTM F3411-22a NetRID DSS interoperability @@ -297,11 +297,6 @@ TODO ASTM NetRID nominal behavior - - NET0470,Table1,14 - Implemented - ASTM NetRID nominal behavior - NET0470,Table1,15 Implemented From 2082388915eff5f93611d593dbd88d95b27d8b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micka=C3=ABl=20Misbach?= Date: Wed, 20 Sep 2023 16:20:43 +0200 Subject: [PATCH 37/37] fix python test --- .../common_dictionary_evaluator_test.py | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py index e7481333bc..36fd354db1 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator_test.py @@ -155,29 +155,19 @@ def test_operator_location(): 2, 1, ), - ( - LatLngPoint( - lat=46.2, - lng=6.1, - ), - Altitude(value=1000.9), # Invalid value - OperatorAltitudeAltitudeType("Takeoff"), - 2, - 1, - ), ( LatLngPoint( lat=46.2, lng=6.1, ), Altitude( - value=1000.9, # Invalid value + value=1000.9, units="FT", # Invalid value reference="UNKNOWN", # Invalid value ), "Takeoff", 2, - 3, + 2, ), ] for invalid_location in invalid_locations: @@ -221,7 +211,7 @@ def step_under_test(self: UnitTestScenario): def test_timestamp(): _assert_timestamp("2023-09-13T04:43:00.1Z", True) # Ok _assert_timestamp("2023-09-13T04:43:00Z", True) # Ok - _assert_timestamp("2023-09-13T04:43:00.501Z", False) # Wrong resolution + _assert_timestamp("2023-09-13T04:43:00.501Z", True) # Ok _assert_timestamp("2023-09-13T04:43:00.1+07:00", False) # Wrong timezone @@ -243,7 +233,7 @@ def test_speed(): _assert_speed(1, True) # Ok _assert_speed(20.75, True) # Ok _assert_speed(400, False) # Fail, above MaxSpeed - _assert_speed(23.3, False) # Wrong resolution + _assert_speed(23.3, True) # Ok def _assert_track(value: float, outcome: bool): @@ -265,7 +255,7 @@ def test_track(): _assert_track(-359, True) # Ok _assert_track(400, False) # Fail, above MaxTrackDirection _assert_track(-360, False) # Fail, below MinTrackDirection - _assert_track(23.3, False) # Wrong resolution + _assert_track(23.3, True) # Wrong resolution _assert_track(SpecialTrackDirection, True) @@ -286,9 +276,7 @@ def step_under_test(self: UnitTestScenario): def test_height(): _assert_height(None, True) # Ok _assert_height(RIDHeight(distance=10, reference="TakeoffLocation"), True) # Ok - _assert_height( - RIDHeight(distance=10.101, reference="TakeoffLocation"), False - ) # Wrong resolution + _assert_height(RIDHeight(distance=10.101, reference="TakeoffLocation"), True) # Ok _assert_height( RIDHeight(distance=10.101, reference="Moon"), False ) # Wrong reference