diff --git a/.github/workflows/hootl.yml b/.github/workflows/hootl.yml index 8b39e7e8e..4a7773b09 100644 --- a/.github/workflows/hootl.yml +++ b/.github/workflows/hootl.yml @@ -56,11 +56,14 @@ jobs: !(python -m ptest runsim -c ptest/configs/hootl_speedup.json -t FailingEmptyCase -ni) !(python -m ptest runsim -c ptest/configs/hootl_speedup.json -t FailingEmptySimCase -ni) - - name: Boot Utility Cases + - name: Mission Rehearsal Cases run: | - python -m ptest runsim -c ptest/configs/hootl.json -t BootToStartupCase -ni - python -m ptest runsim -c ptest/configs/hootl.json -t BootToDetumbleCase -ni - python -m ptest runsim -c ptest/configs/hootl.json -t BootToStandbyCase -ni + python -m ptest runsim -c ptest/configs/hootl.json -t SingleSatStartupCase -ni + python -m ptest runsim -c ptest/configs/hootl_hootl.json -t DualSatStartupCase -ni + python -m ptest runsim -c ptest/configs/hootl.json -t SingleSatStartupCase -ni + python -m ptest runsim -c ptest/configs/hootl_hootl.json -t DualSatStartupCase -ni + python -m ptest runsim -c ptest/configs/hootl.json -t SingleSatStartupCase -ni + python -m ptest runsim -c ptest/configs/hootl_hootl.json -t DualSatStartupCase -ni - name: Hardware Checkout Cases run: | diff --git a/ptest/cases/__init__.py b/ptest/cases/__init__.py index cf20229fc..25e3f3513 100644 --- a/ptest/cases/__init__.py +++ b/ptest/cases/__init__.py @@ -2,19 +2,25 @@ # - tools/alltest.sh # - .github/workflows/hootl.yml -# Testcases that require simulation from .empty_case import EmptySimCase, FailingEmptySimCase from .dual_empty_case import DualEmptySimCase from .piksi_fault_handler import PiksiFaultHandler from .autonomous_mission_manager_pure_radio import AutonomousMissionController -# from .base import * +# Testcases intended for use with mission rehearsals and general telemetry +# testing. +from .mission import ( + SingleSatStartupCase, + DualSatStartupCase, + SingleSatDetumbleCase, + DualSatDetumbleCase, + SingleSatStandbyCase, + DualSatStandbyCase +) from .psim_debug import PSimDebug from .dual_psim import DualPSim -# Testcases that don't require simulation -from .boot_to import BootToStartupCase, BootToDetumbleCase, BootToStandbyCase from .empty_case import EmptyCase, FailingEmptyCase from .dual_empty_case import DualEmptyCase from .ditl_case import DitlCase diff --git a/ptest/cases/boot_to.py b/ptest/cases/boot_to.py deleted file mode 100644 index a0bb16d34..000000000 --- a/ptest/cases/boot_to.py +++ /dev/null @@ -1,53 +0,0 @@ -from .base import SingleSatCase, PSimCase -from .utils import TestCaseFailure -import lin - -class BootToStartupCase(SingleSatCase, PSimCase): - - def __init__(self, *args, **kwargs): - super(BootToStartupCase, self).__init__(*args, **kwargs) - - self.psim_configs += ["truth/deployment"] - self.initial_state = "startup" - - def run(self): - if self.mission_state != "startup": - raise TestCaseFailure("Testcase failed to boot into startup") - - self.finish() - - -class BootToDetumbleCase(SingleSatCase, PSimCase): - - def __init__(self, *args, **kwargs): - super(BootToDetumbleCase, self).__init__(*args, **kwargs) - - self.psim_configs += ["truth/detumble"] - self.initial_state = "detumble" - self.skip_deployment_wait = True - - def run(self): - if self.mission_state != "detumble": - raise TestCaseFailure("Testcase failed to boot into detumble") - - self.finish() - - -class BootToStandbyCase(SingleSatCase, PSimCase): - - def __init__(self, *args, **kwargs): - super(BootToStandbyCase, self).__init__(*args, **kwargs) - - self.psim_configs += ["truth/standby"] - self.psim_config_overrides["truth.leader.attitude.w"] = lin.Vector3([0.01,0.071,-0.01]) - - self.initial_state = "standby" - self.skip_deployment_wait = True - - def run(self): - if self.mission_state != "standby": - raise TestCaseFailure("Testcase failed to boot into standby") - if not self.rs("attitude_estimator.valid"): - raise TestCaseFailure("Attitude estimator never became valid") - - self.finish() diff --git a/ptest/cases/mission/__init__.py b/ptest/cases/mission/__init__.py new file mode 100644 index 000000000..07c1bf01e --- /dev/null +++ b/ptest/cases/mission/__init__.py @@ -0,0 +1,3 @@ +from .startup import SingleSatStartupCase, DualSatStartupCase +from .detumble import SingleSatDetumbleCase, DualSatDetumbleCase +from .standby import SingleSatStandbyCase, DualSatStandbyCase diff --git a/ptest/cases/mission/detumble.py b/ptest/cases/mission/detumble.py new file mode 100644 index 000000000..642ba0272 --- /dev/null +++ b/ptest/cases/mission/detumble.py @@ -0,0 +1,45 @@ +from ..base import DualSatCase, SingleSatCase, PSimCase +from .utils import log_fc_data, log_psim_data + + +class SingleSatDetumbleCase(SingleSatCase, PSimCase): + + def __init__(self, *args, **kwargs): + super(SingleSatDetumbleCase, self).__init__(*args, **kwargs) + + self.psim_configs += ["truth/detumble"] + self.initial_state = "detumble" + self.skip_deployment_wait = True + + def run(self): + if not self.is_interactive: + self.finish() + return + + log_fc_data(self.flight_controller) + log_psim_data(self, "leader") + + self.cycle() + + +class DualSatDetumbleCase(DualSatCase, PSimCase): + + def __init__(self, *args, **kwargs): + super(DualSatDetumbleCase, self).__init__(*args, **kwargs) + + self.psim_configs += ["truth/detumble"] + self.leader_initial_state = "detumble" + self.follower_initial_state = "detumble" + self.leader_skip_deployment_wait = True + self.follower_skip_deployment_wait = True + + def run(self): + if not self.is_interactive: + self.finish() + return + + log_fc_data(self.flight_controller_leader) + log_fc_data(self.flight_controller_follower) + log_psim_data(self, "leader", "follower") + + self.cycle() diff --git a/ptest/cases/mission/standby.py b/ptest/cases/mission/standby.py new file mode 100644 index 000000000..e3a712c39 --- /dev/null +++ b/ptest/cases/mission/standby.py @@ -0,0 +1,59 @@ +from ..base import DualSatCase, SingleSatCase, PSimCase +from ..utils import TestCaseFailure +from .utils import log_fc_data, log_psim_data + +import lin + + +class SingleSatStandbyCase(SingleSatCase, PSimCase): + + def __init__(self, *args, **kwargs): + super(SingleSatStandbyCase, self).__init__(*args, **kwargs) + + self.psim_configs += ["truth/standby"] + self.psim_config_overrides["truth.leader.attitude.w"] = lin.Vector3([0.01,0.0711,-0.01]) + self.initial_state = "standby" + self.skip_deployment_wait = True + + def run(self): + if not self.is_interactive: + if not self.flight_controller.smart_read("attitude_estimator.valid"): + raise TestCaseFailure("Attitude estimator failed to initialize.") + + self.finish() + return + + log_fc_data(self.flight_controller) + log_psim_data(self, "leader") + + self.cycle() + + +class DualSatStandbyCase(DualSatCase, PSimCase): + + def __init__(self, *args, **kwargs): + super(DualSatStandbyCase, self).__init__(*args, **kwargs) + + self.psim_configs += ["truth/standby"] + self.psim_config_overrides["truth.leader.attitude.w"] = lin.Vector3([0.01,0.0711,-0.01]) + self.psim_config_overrides["truth.follower.attitude.w"] = lin.Vector3([0.01,0.0711,-0.01]) + self.leader_initial_state = "standby" + self.follower_initial_state = "standby" + self.leader_skip_deployment_wait = True + self.follower_skip_deployment_wait = True + + def run(self): + if not self.is_interactive: + if not self.flight_controller_leader.smart_read("attitude_estimator.valid"): + raise TestCaseFailure("Leader attitude estimator failed to initialize.") + if not self.flight_controller_follower.smart_read("attitude_estimator.valid"): + raise TestCaseFailure("Follower attitude estimator failed to initialize.") + + self.finish() + return + + log_fc_data(self.flight_controller_leader) + log_fc_data(self.flight_controller_follower) + log_psim_data(self, "leader", "follower") + + self.cycle() diff --git a/ptest/cases/mission/startup.py b/ptest/cases/mission/startup.py new file mode 100644 index 000000000..32265cd05 --- /dev/null +++ b/ptest/cases/mission/startup.py @@ -0,0 +1,42 @@ +from ..base import DualSatCase, SingleSatCase, PSimCase +from .utils import log_fc_data, log_psim_data + + +class SingleSatStartupCase(SingleSatCase, PSimCase): + + def __init__(self, *args, **kwargs): + super(SingleSatStartupCase, self).__init__(*args, **kwargs) + + self.psim_configs += ["truth/deployment"] + self.initial_state = "startup" + + def run(self): + if not self.is_interactive: + self.finish() + return + + log_fc_data(self.flight_controller) + log_psim_data(self, "leader") + + self.cycle() + + +class DualSatStartupCase(DualSatCase, PSimCase): + + def __init__(self, *args, **kwargs): + super(DualSatStartupCase, self).__init__(*args, **kwargs) + + self.psim_configs += ["truth/deployment"] + self.leader_initial_state = "startup" + self.follower_initial_state = "startup" + + def run(self): + if not self.is_interactive: + self.finish() + return + + log_fc_data(self.flight_controller_leader) + log_fc_data(self.flight_controller_follower) + log_psim_data(self, "leader", "follower") + + self.cycle() diff --git a/ptest/cases/mission/utils.py b/ptest/cases/mission/utils.py new file mode 100644 index 000000000..1ed2d3b72 --- /dev/null +++ b/ptest/cases/mission/utils.py @@ -0,0 +1,71 @@ +"""Utility functions specific to full mission cases. + +Full mission cases here refers to those intended to be used for mission +rehearsals. +""" + +fc_fields = [ + # Core state information + "pan.state", + "pan.bootcount", + "pan.cycle_no", + "pan.deployment.elapsed", + # Other state machine states + "adcs.state", + "piksi.state", + "radio.state", + # Estimator information + "time.valid", + "time.gps", + "orbit.valid", + "orbit.pos", + "orbit.vel", + "rel_orbit.state", + "rel_orbit.rel_pos", + "rel_orbit.rel_vel", + "attitude_estimator.valid", + "attitude_estimator.q_body_eci", + "attitude_estimator.L_body", + "attitude_estimator.w_bias_body", + # Attitude control commands + "adcs_cmd.mtr_cmd", + "adcs_cmd.rwa_torque_cmd", + # PSim sensor debugging information +# "piksi.time" +# "adcs_monitor.ssa_vec", +# "adcs_monitor.mag1_vec", +# "adcs_monitor.gyr_vec", +# "adcs_monitor.ssa_mode", +] + +psim_fields_per_satellite = [ + "truth.{}.attitude.w", + "truth.{}.attitude.L", + "truth.{}.wheels.t", + "truth.{}.wheels.w", + "truth.{}.orbit.r.ecef", + "truth.{}.orbit.v.ecef", + "sensors.{}.gyroscope.w.bias" +] + +psim_fields = [ + "truth.t.ns", + "truth.dt.ns" +] + + +def log_fc_data(fc): + for field in fc_fields: + fc.smart_read(field) + + +def log_psim_data(case, *satellites): + """Logs a collection of PSim associated with a testcase for the satellite(s) + specified. + """ + for satellite in satellites: + for field in psim_fields_per_satellite: + case.rs_psim(field.format(satellite)) + + for field in psim_fields: + case.rs_psim(field)