diff --git a/communications/command_definitions.py b/communications/command_definitions.py index 9bc12573..36aa0b75 100644 --- a/communications/command_definitions.py +++ b/communications/command_definitions.py @@ -106,6 +106,10 @@ def __init__(self, parent: MainSatelliteThread): NormalCommandEnum.SudoCommand.value: self.sudo_command, NormalCommandEnum.Picberry.value: self.picberry, NormalCommandEnum.ExecPyFile.value: self.exec_py_file, + NormalCommandEnum.IgnoreLowBatt.value: self.ignore_low_battery, + NormalCommandEnum.MissionMode.value: self.set_mission_mode, + NormalCommandEnum.InitOpnav.value: self.init_opnav_parameters, + NormalCommandEnum.SetCommsOffTimes: self.set_comms_off_times } self.low_battery_commands = { @@ -131,11 +135,8 @@ def __init__(self, parent: MainSatelliteThread): self.sensor_commands = {} self.test_commands = { - 2: self.split, - 3: self.run_opnav, TestCommandEnum.ADCTest.value: self.adc_test, TestCommandEnum.SeparationTest.value: self.separation_test, - 6: self.gom_outputs, 7: self.comms_driver_test, TestCommandEnum.LongString.value: self.print_long_string, TestCommandEnum.PiShutdown.value: self.pi_shutdown, @@ -160,6 +161,8 @@ def __init__(self, parent: MainSatelliteThread): CommandCommandEnum.ShellCommand.value: self.shell_command } + self.attitude_commands = {} + self.COMMAND_DICT = { FMEnum.Boot.value: self.bootup_commands, FMEnum.Restart.value: self.restart_commands, @@ -171,7 +174,8 @@ def __init__(self, parent: MainSatelliteThread): FMEnum.SensorMode.value: self.sensor_commands, FMEnum.TestMode.value: self.test_commands, FMEnum.CommsMode: self.comms_commands, - FMEnum.Command.value: self.command_commands + FMEnum.Command.value: self.command_commands, + FMEnum.AttitudeAdjustment.value: self.attitude_commands } for value in self.COMMAND_DICT.values(): @@ -277,13 +281,9 @@ def set_parameter(self, **kwargs): def set_exit_lowbatt_threshold(self, **kwargs): """Does the same thing as set_parameter, but only for the EXIT_LOW_BATTERY_MODE_THRESHOLD parameter. Only requires one kwarg and does some basic sanity checks on the passed value""" - value = kwargs['value'] + value = kwargs['vbatt'] try: - assert 0 < value < 1.0 and float(value) is float - if value >= params.ENTER_LOW_BATTERY_MODE_THRESHOLD: - self.parent.logger.error( - f"New value for Exit LB thresh must be less than current Enter LB thresh value") - assert False + assert 4 < value < 8.5 and float(value) is float self.set_parameter(name="EXIT_LOW_BATTERY_MODE_THRESHOLD", value=value) except AssertionError: self.parent.logger.error(f"Incompatible value {value} for EXIT_LOW_BATTERY_MODE_THRESHOLD") @@ -340,8 +340,14 @@ def electrolysis(self, **kwargs): state = kwargs[STATE] delay = kwargs.get(DELAY, 0) assert type(state) is bool + params.WANT_TO_ELECTROLYZE = state self.parent.gom.set_electrolysis(state, delay=delay) + def ignore_low_battery(self, **kwargs): + """This is obviously a very dangerous command. It's mainly meant for testing on the ground""" + ignore = kwargs[IGNORE] + params.IGNORE_LOW_BATTERY = ignore + # def burn(self, **kwargs): # time_burn = kwargs['time'] # absolute = kwargs['absolute'] @@ -370,11 +376,13 @@ def reboot_pi(): os.system("reboot") # add something here that adds to the restarts db that this restart was commanded - def cease_comms(self): - # I'm actually unsure of how to do this. Maybe do something with the GPIO pins so that the pi can't transmit - self.parent.logger.critical("Ceasing all communications") - # definitely should implement some sort of password and double verification to prevent accidental triggering - raise NotImplementedError + def cease_comms(self, **kwargs): + pword = kwargs[PASSWORD] + if pword == 123: # TODO, change + # I'm actually unsure of how to do this. Maybe do something with the GPIO pins so that the pi can't transmit + self.parent.logger.critical("Ceasing all communications") + # definitely should implement some sort of password and double verification to prevent accidental triggering + raise NotImplementedError def set_system_clock(self, **kwargs): # Needs validation (talk to Viraj) # need to validate this works, and need to integrate updating RTC @@ -661,14 +669,6 @@ def get_gom_conf2(self, **kwargs): # **current_config2_dict) # self.parent.downlink_queue.put(acknowledgement) - def get_gom_conf1(self, **kwargs): - raise NotImplementedError - - def set_gom_conf2(self, **kwargs): - raise NotImplementedError - - def get_gom_conf2(self, **kwargs): - raise NotImplementedError def shell_command(self, **kwargs): cmd: str = kwargs.get(CMD) @@ -698,6 +698,32 @@ def exec_py_file(self, **kwargs): self.parent.logger.debug(f"CWD: {os.getcwd()}") exec(open(filename).read()) + def set_mission_mode(self, **kwargs): + mission_mode = kwargs[MISSION_MODE] + self.set_parameter(name='CURRENT_MISSION_MODE', value=mission_mode, hard_set=True) + self.parent.flight_mode.update_mission_mode(mission_mode) + + return {MISSION_MODE: params.CURRENT_MISSION_MODE} + + def init_opnav_parameters(self, **kwargs): + pos_x = kwargs[POSITION_X] + pos_y = kwargs[POSITION_Y] + pos_z = kwargs[POSITION_Z] + pos_t = kwargs[NASA_PROVIDED_TIME] + + # TODO write above values to opnav db + + self.set_parameter(name="WANT_TO_OPNAV", value=True, hard_set=True) + + def set_comms_off_times(self, **kwargs): + comms_off_start = kwargs[COMMS_OFF_START] + comms_off_end = kwargs[COMMS_OFF_END] + + self.set_parameter(name="COMMS_OFF_TIME_START", value=comms_off_start, hard_set=True) + self.set_parameter(name="COMMS_OFF_TIME_END", value=comms_off_end, hard_set=True) + + return {COMMS_OFF_START: comms_off_start, COMMS_OFF_END: comms_off_end} + def dict_from_eps_config(config: ps.eps_config_t) -> dict: return {PPT_MODE: config.ppt_mode, diff --git a/flight_modes/attitude_adjustment.py b/flight_modes/attitude_adjustment.py index d66b8c4a..9d188a9d 100644 --- a/flight_modes/attitude_adjustment.py +++ b/flight_modes/attitude_adjustment.py @@ -38,7 +38,7 @@ def C3(theta): """ class AAMode(PauseBackgroundMode): - """Attitude adjustment flight mode""" + """FMID 11: Attitude adjustment flight mode""" flight_mode_id = FMEnum.AttitudeAdjustment.value command_codecs = {} diff --git a/flight_modes/flight_mode.py b/flight_modes/flight_mode.py index 50bf552d..9e419a37 100644 --- a/flight_modes/flight_mode.py +++ b/flight_modes/flight_mode.py @@ -13,6 +13,7 @@ from multiprocessing import Process import subprocess from queue import Empty +from traceback import format_tb from utils.constants import * @@ -31,6 +32,8 @@ logger = get_log() +NONE = ([], 0) + class FlightMode: # Override in Subclasses to tell CommandHandler the functions and arguments this flight mode takes @@ -115,7 +118,6 @@ def run_mode(self): raise NotImplementedError("Only implemented in specific flight mode subclasses") def execute_commands(self): - bogus = bool() if len(self.parent.commands_to_execute) == 0: pass # If I have no commands to execute do nothing else: @@ -164,11 +166,50 @@ def completed_task(self): # Autonomous actions to maintain safety def automatic_actions(self): - pass + if not params.BURNWIRES_FIRED and self.parent.telemetry.rtc.rtc_time - params.INITIAL_TIME > ( + 3600 * BURNWIRE_WAITIME): + self.parent.logger.info("Actuating burnwires after 3 hours now.") + self.parent.gom.burnwire1(params.SPLIT_BURNWIRE_DURATION) + self.parent.command_definitions.set_parameter(name="BURNWIRES_FIRED", value=True, hard_set=True) + + if self.parent.telemetry.gom.hk.outputs[GomOutputs.comms.value] == 0 and \ + not (params.COMMS_OFF_TIME_START < time() < params.COMMS_OFF_TIME_END): + self.parent.logger.info("Autonomously Turning on LNA to receive commands") + self.parent.gom.lna(True) def write_telemetry(self): pass + def update_mission_mode(self, mission_mode_id: int): + """Sets the behavior of flight modes. Acts like a meta-flight mode.""" + if mission_mode_id != params.CURRENT_MISSION_MODE: + self.parent.command_definitions.set_parameter(name='CURRENT_MISSION_MODE', value=mission_mode_id, + hard_set=True) + if mission_mode_id == MissionModeEnum.Boot.value: + # set parameters for boot + # TODO bulk parameters hard set? + self.parent.command_definitions.set_parameter(name='WANT_TO_ELECTROLYZE', value=False, + hard_set=True) + self.parent.command_definitions.set_parameter(name='WANT_TO_OPNAV', value=False, + hard_set=True) + self.parent.command_definitions.set_parameter(name='TELEM_DOWNLINK_TIME', value=10, + hard_set=True) + # self.parent.command_definitions.set_parameter(name='CURRENT_MISSION_MODE', value=mission_mode_id, + # hard_set=True) + + if mission_mode_id == MissionModeEnum.Normal.value: + # set parameters for normal mission mode + # TODO bulk parameters hard set? + self.parent.command_definitions.set_parameter(name='WANT_TO_ELECTROLYZE', value=True, + hard_set=True) + # self.parent.command_definitions.set_parameter(name='WANT_TO_OPNAV', value=True, + # hard_set=True) + # Only want to run opnav once we get initial position and time update command + self.parent.command_definitions.set_parameter(name='TELEM_DOWNLINK_TIME', value=60, + hard_set=True) + # self.parent.command_definitions.set_parameter(name='CURRENT_MISSION_MODE', value=mission_mode_id, + # hard_set=True) + def __enter__(self): logger.debug(f"Starting flight mode {self.flight_mode_id}") return self @@ -177,7 +218,7 @@ def __exit__(self, exc_type, exc_value, tb): logger.debug(f"Finishing flight mode {self.flight_mode_id}") if exc_type is not None: logger.error(f"Flight Mode failed with error type {exc_type} and value {exc_value}") - logger.error(f"Failed with traceback:\n {tb}") + logger.error(f"Failed with traceback:\n {format_tb(tb)}") # Model for FlightModes that require precise timing @@ -206,6 +247,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): class TestMode(PauseBackgroundMode): + """FMID 8""" flight_mode_id = FMEnum.TestMode.value def __init__(self, parent): @@ -217,11 +259,12 @@ def update_state(self): def run_mode(self): pass - command_codecs = {TestCommandEnum.SeparationTest.value: ([], 0), - TestCommandEnum.ADCTest.value: ([], 0), - TestCommandEnum.CommsDriver.value: ([], 0), - TestCommandEnum.PiShutdown.value: ([], 0), - TestCommandEnum.RTCTest.value: ([], 0), + command_codecs = {TestCommandEnum.Switch.value: NONE, + TestCommandEnum.SeparationTest.value: NONE, + TestCommandEnum.ADCTest.value: NONE, + TestCommandEnum.CommsDriver.value: NONE, + TestCommandEnum.PiShutdown.value: NONE, + TestCommandEnum.RTCTest.value: NONE, TestCommandEnum.LongString.value: (['some_number', 'long_string'], 180) } @@ -240,7 +283,9 @@ def run_mode(self): class CommsMode(FlightMode): + """FMID 9""" flight_mode_id = FMEnum.CommsMode.value + command_codecs = {CommsCommandEnum.Switch.value: NONE} def __init__(self, parent): super().__init__(parent) @@ -304,7 +349,9 @@ def run_mode(self): class OpNavMode(FlightMode): + """FMID 5""" flight_mode_id = FMEnum.OpNav.value + command_codecs = {OpNavCommandEnum.Switch.value: NONE} def __init__(self, parent): super().__init__(parent) @@ -337,7 +384,9 @@ def opnav_subprocess(self, q): class SensorMode(FlightMode): + """FMID 7""" flight_mode_id = FMEnum.SensorMode.value + command_codecs = {SensorsCommandEnum.Switch.value: NONE} def __init__(self, parent): super().__init__(parent) @@ -348,7 +397,18 @@ def update_state(self): class LowBatterySafetyMode(FlightMode): + """FMID 3""" flight_mode_id = FMEnum.LowBatterySafety.value + command_codecs = {LowBatterySafetyCommandEnum.Switch.value: NONE, + LowBatterySafetyCommandEnum.BasicTelem.value: NONE, + LowBatterySafetyCommandEnum.CritTelem.value: NONE, + LowBatterySafetyCommandEnum.DetailedTelem.value: NONE, + LowBatterySafetyCommandEnum.ExitLBSafetyMode: NONE, + LowBatterySafetyCommandEnum.SetExitLBSafetyMode: ([VBATT], 2), + LowBatterySafetyCommandEnum.SetParam: ([NAME, VALUE, HARD_SET], 33) + } + + command_arg_types = {VBATT: 'short'} def __init__(self, parent): super().__init__(parent) @@ -370,8 +430,9 @@ def __enter__(self): class ManeuverMode(PauseBackgroundMode): + """FMID 6""" flight_mode_id = FMEnum.Maneuver.value - command_codecs = {} + command_codecs = {ManeuverCommandEnum.Switch.value: NONE} command_arg_unpackers = {} def __init__(self, parent): @@ -396,8 +457,16 @@ def run_mode(self): # TODO class SafeMode(FlightMode): + """FMID 4""" flight_mode_id = FMEnum.Safety.value + command_codecs = {SafetyCommandEnum.Switch.value: NONE, + SafetyCommandEnum.DetailedTelem.value: NONE, + SafetyCommandEnum.CritTelem.value: NONE, + SafetyCommandEnum.BasicTelem.value: NONE, + SafetyCommandEnum.ExitSafetyMode.value: NONE, + SafetyCommandEnum.SetParameter.value: ([NAME, VALUE, HARD_SET], 33), } + def __init__(self, parent): super().__init__(parent) @@ -413,15 +482,19 @@ def register_commands(cls): flight_mode_id = FMEnum.Normal.value command_codecs = { - NormalCommandEnum.Switch.value: ([], 0), - NormalCommandEnum.RunOpNav.value: ([], 0), + NormalCommandEnum.Switch.value: NONE, + NormalCommandEnum.RunOpNav.value: NONE, # NormalCommandEnum.SetDesiredAttitude.value: ([AZIMUTH, ELEVATION], 8), # NormalCommandEnum.SetAccelerate.value: ([ACCELERATE], 1), - # NormalCommandEnum.SetBreakpoint.value: ([], 0), # TODO define exact parameters + # NormalCommandEnum.SetBreakpoint.value: NONE, # TODO define exact parameters NormalCommandEnum.SetParam.value: ([NAME, VALUE, HARD_SET], 33), + NormalCommandEnum.CritTelem.value: NONE, + NormalCommandEnum.BasicTelem.value: NONE, + NormalCommandEnum.DetailedTelem.value: NONE, NormalCommandEnum.SetElectrolysis.value: ([STATE, DELAY], 5), NormalCommandEnum.SetOpnavInterval.value: ([INTERVAL], 4), NormalCommandEnum.Verification.value: ([NUM_BLOCKS], 2), + NormalCommandEnum.GetParam.value: ([INDEX], 2), NormalCommandEnum.ScheduleManeuver.value: ([TIME], 4), NormalCommandEnum.ACSPulsing.value: ([START, PULSE_DURATION, PULSE_NUM, PULSE_DT], 14), NormalCommandEnum.NemoWriteRegister.value: ([REG_ADDRESS, REG_VALUE], 2), @@ -445,9 +518,9 @@ def register_commands(cls): RATE_DATA_ROTATE_PERIOD, HISTOGRAM_ROTATE_PERIOD, ], 32), - NormalCommandEnum.NemoPowerOff.value: ([], 0), - NormalCommandEnum.NemoPowerOn.value: ([], 0), - NormalCommandEnum.NemoReboot.value: ([], 0), + NormalCommandEnum.NemoPowerOff.value: NONE, + NormalCommandEnum.NemoPowerOn.value: NONE, + NormalCommandEnum.NemoReboot.value: NONE, NormalCommandEnum.NemoProcessRateData.value: ([T_START, T_STOP, DECIMATION_FACTOR], 9), NormalCommandEnum.NemoProcessHistograms.value: ([T_START, T_STOP, DECIMATION_FACTOR], 9), NormalCommandEnum.GomConf1Set.value: ([PPT_MODE, BATTHEATERMODE, BATTHEATERLOW, BATTHEATERHIGH, OUTPUT_NORMAL1, @@ -461,16 +534,18 @@ def register_commands(cls): NormalCommandEnum.ShellCommand.value: ([CMD], 24), NormalCommandEnum.SudoCommand.value: ([CMD], 24), NormalCommandEnum.Picberry.value: ([CMD], 24), - NormalCommandEnum.GomConf1Get.value: ([], 0), + NormalCommandEnum.GomConf1Get.value: NONE, NormalCommandEnum.GomConf2Set.value: ([MAX_VOLTAGE, NORM_VOLTAGE, SAFE_VOLTAGE, CRIT_VOLTAGE], 8), - NormalCommandEnum.GomConf2Get.value: ([], 0), - NormalCommandEnum.ExecPyFile.value: ([FNAME], 36) + NormalCommandEnum.GomConf2Get.value: NONE, + NormalCommandEnum.ExecPyFile.value: ([FNAME], 36), + NormalCommandEnum.IgnoreLowBatt.value: ([IGNORE], 1), + NormalCommandEnum.MissionMode.value: ([MISSION_MODE], 1), + NormalCommandEnum.InitOpnav.value: ([POSITION_X, POSITION_Y, POSITION_Z, NASA_PROVIDED_TIME], 32), + NormalCommandEnum.SetCommsOffTimes.value: ([COMMS_OFF_START, COMMS_OFF_END], 8) } command_arg_types = { - AZIMUTH: 'float', - ELEVATION: 'float', - ACCELERATE: 'bool', + AZIMUTH: 'float', ELEVATION: 'float', ACCELERATE: 'bool', NAME: 'string', VALUE: 'double', STATE: 'bool', @@ -510,30 +585,17 @@ def register_commands(cls): BATTHEATERMODE: "bool", BATTHEATERLOW: "uint8", BATTHEATERHIGH: "uint8", - OUTPUT_NORMAL1: "bool", - OUTPUT_NORMAL2: "bool", - OUTPUT_NORMAL3: "bool", - OUTPUT_NORMAL4: "bool", - OUTPUT_NORMAL5: "bool", - OUTPUT_NORMAL6: "bool", - OUTPUT_NORMAL7: "bool", - OUTPUT_NORMAL8: "bool", - OUTPUT_SAFE1: "bool", - OUTPUT_SAFE2: "bool", - OUTPUT_SAFE3: "bool", - OUTPUT_SAFE4: "bool", - OUTPUT_SAFE5: "bool", - OUTPUT_SAFE6: "bool", - OUTPUT_SAFE7: "bool", - OUTPUT_SAFE8: "bool", + OUTPUT_NORMAL1: "bool", OUTPUT_NORMAL2: "bool", OUTPUT_NORMAL3: "bool", OUTPUT_NORMAL4: "bool", + OUTPUT_NORMAL5: "bool", OUTPUT_NORMAL6: "bool", OUTPUT_NORMAL7: "bool", OUTPUT_NORMAL8: "bool", + OUTPUT_SAFE1: "bool", OUTPUT_SAFE2: "bool", OUTPUT_SAFE3: "bool", OUTPUT_SAFE4: "bool", + OUTPUT_SAFE5: "bool", OUTPUT_SAFE6: "bool", OUTPUT_SAFE7: "bool", OUTPUT_SAFE8: "bool", OUTPUT_ON_DELAY: "short", OUTPUT_OFF_DELAY: "short", VBOOST1: "short", VBOOST2: "short", VBOOST3: "short", - MAX_VOLTAGE: 'short', - NORM_VOLTAGE: 'short', - SAFE_VOLTAGE: 'short', - CRIT_VOLTAGE: 'short', - CMD: 'string', - FNAME: 'string' + MAX_VOLTAGE: 'short', NORM_VOLTAGE: 'short', SAFE_VOLTAGE: 'short', CRIT_VOLTAGE: 'short', + FNAME: 'string', + CMD: 'string', IGNORE: 'bool', MISSION_MODE: 'uint8', + POSITION_X: 'double', POSITION_Y: 'double', POSITION_Z: 'double', NASA_PROVIDED_TIME: 'double', + COMMS_OFF_START: 'int', COMMS_OFF_END: 'int' } downlink_codecs = { @@ -605,6 +667,9 @@ def register_commands(cls): def __init__(self, parent): super().__init__(parent) + def poll_inputs(self): + super().poll_inputs() + def update_state(self): super_fm = super().update_state() @@ -618,6 +683,9 @@ def update_state(self): # if we don't want to electrolyze (per GS command), set need_to_electrolyze to false need_to_electrolyze = need_to_electrolyze and params.WANT_TO_ELECTROLYZE + # if we don't want to run opnav (per GS command), set time_for_opnav to false + time_for_opnav = time_for_opnav and params.WANT_TO_OPNAV + # if currently electrolyzing and over pressure, stop electrolyzing if currently_electrolyzing and not need_to_electrolyze: self.parent.gom.set_electrolysis(False) @@ -644,6 +712,18 @@ def update_state(self): def run_mode(self): logger.info(f"In NORMAL flight mode") + if not params.FOR_FLIGHT: + # log relevant data + gom_voltage = self.parent.tlm.gom.hk.vbatt + current_in = self.parent.tlm.gom.hk.cursun + current_out = self.parent.tlm.gom.hk.cursys + pressure = self.parent.tlm.prs.pressure + poll_time = self.parent.tlm.poll_time + data = [poll_time, gom_voltage, current_in, current_out, pressure] + data_str = ','.join([str(elem) for elem in data]) + with open("fill_and_fire.csv", 'a') as ff: + ff.write(data_str) + self.completed_task() @@ -653,17 +733,32 @@ class CommandMode(PauseBackgroundMode): flight_mode_id = FMEnum.Command.value command_codecs = { + CommandCommandEnum.Switch.value: NONE, + CommandCommandEnum.SetParam.value: ([NAME, VALUE, HARD_SET], 33), + CommandCommandEnum.SetSystemTime: ([TIME], 8), + CommandCommandEnum.RebootPi: NONE, + CommandCommandEnum.RebootGom: NONE, + CommandCommandEnum.PowerCycle: NONE, + CommandCommandEnum.GomPin: ([OUTPUT_CHANNEL, STATE, DELAY], 4), CommandCommandEnum.AddFileBlock.value: ([FILE_PATH, BLOCK_NUMBER, BLOCK_TEXT], 195 - MIN_COMMAND_SIZE), CommandCommandEnum.GetFileBlocksInfo.value: ([FILE_PATH, TOTAL_BLOCKS], 52), CommandCommandEnum.ActivateFile.value: ([FILE_PATH, TOTAL_BLOCKS], 52), - CommandCommandEnum.ShellCommand.value: ([CMD], 24) + CommandCommandEnum.ShellCommand.value: ([CMD], 24), + CommandCommandEnum.GeneralCmd.value: ([CMD], 24), # TODO + CommandCommandEnum.GomGeneralCmd.value: ([CMD], 24), # TODO + CommandCommandEnum.CeaseComms.value: ([PASSWORD], 8) } command_arg_types = { FILE_PATH: 'string', BLOCK_NUMBER: 'short', BLOCK_TEXT: 'string', - TOTAL_BLOCKS: 'short' + TOTAL_BLOCKS: 'short', + SYS_TIME: 'double', + GOM_PIN_STATE: 'bool', + GOM_PIN_DELAY: 'short', + OUTPUT_CHANNEL: 'uint8', + PASSWORD: 'long' } downlink_codecs = { diff --git a/flight_modes/restart_reboot.py b/flight_modes/restart_reboot.py index 753fc82f..9c1db8da 100644 --- a/flight_modes/restart_reboot.py +++ b/flight_modes/restart_reboot.py @@ -5,14 +5,22 @@ from flight_modes.flight_mode import FlightMode import os from utils.log import get_log -from utils.constants import FMEnum, BootCommandEnum, RestartCommandEnum +from utils.constants import FMEnum, BootCommandEnum, RestartCommandEnum, MissionModeEnum import psutil +import utils.parameters as params +import glob logger = get_log() + class BootUpMode(FlightMode): + """FMID 0""" flight_mode_id = FMEnum.Boot.value - command_codecs = {BootCommandEnum.Split.value: ([], 0)} + command_codecs = { + BootCommandEnum.Switch.value: ([], 0), + BootCommandEnum.Split.value: ([], 0) + } + command_arg_unpackers = {} def __init__(self, parent): @@ -26,11 +34,13 @@ def run_mode(self): self.session = create_session() self.log() + self.set_parameters() - # deploy antennae - # FIXME: differentiate between Hydrogen and Oxygen. Each satellite now has different required Bootup behaviors - logger.info("Antennae deploy...") - self.parent.gom.burnwire1(5) + if params.SPACECRAFT_NAME == 'HYDROGEN': + # deploy antennae + logger.info("Deploying antennae on Hydrogen") + self.parent.gom.burnwire1(params.SPLIT_BURNWIRE_DURATION) + self.parent.command_definitions.set_parameter(name="BURNWIRES_FIRED", value=True, hard_set=True) if self.parent.need_to_reboot: # TODO: double check the boot db history to make sure we aren't going into a boot loop @@ -49,9 +59,15 @@ def log(self): def update_state(self) -> int: return NO_FM_CHANGE + def set_parameters(self): + self.parent.command_definitions.set_parameter(name="SPACECRAFT_NAME", value=determine_name(), hard_set=True) + self.parent.logger.info(f"Spacecraft is {params.SPACECRAFT_NAME}") + self.update_mission_mode(MissionModeEnum.Boot.value) + class RestartMode(FlightMode): - command_codecs = {} + """FMID 1""" + command_codecs = {RestartCommandEnum.Switch.value: ([], 0)} command_arg_unpackers = {} flight_mode_id = FMEnum.Restart.value @@ -84,3 +100,27 @@ def run_mode(self): def update_state(self) -> int: return super().update_state() + + +def get_hostname(): + # thank you https://stackoverflow.com/questions/4271740/how-can-i-use-python-to-get-the-system-hostname + # Does not work on windows + return os.uname()[1] + + +def determine_name(): + """Checks if Hydrogen or oxygen""" + filenames = glob.glob('../utils/*_parameters.py') + if 'hydrogen' in ','.join(filenames): + space_name = 'HYDROGEN' + else: + space_name = 'OXYGEN' + + # if in flight, double check the hostname + # Want to assume oxygen in worst case scenario (don't want to split prematurely!) + if params.FOR_FLIGHT: + hostname = get_hostname() + if 'oxygen' in hostname and space_name == 'HYDROGEN': + space_name = 'OXYGEN' + + return space_name diff --git a/main.py b/main.py index 2ae2887b..cc735888 100644 --- a/main.py +++ b/main.py @@ -68,16 +68,18 @@ def __init__(self): self.log_dir = LOG_DIR self.logger = get_log() self.attach_sigint_handler() # FIXME + self.attach_sigterm_handler() self.file_block_bank = {} self.need_to_reboot = False - self.gom = None - self.gyro = None - self.adc = None - self.rtc = None - self.radio = None - self.mux = None - self.camera = None + self.gom = Gomspace + self.gyro = GyroSensor + self.adc = ADC + self.rtc = RTC + self.radio = Radio + self.mux = camera.CameraMux + self.camera = camera.Camera + self.nemo_manager = NemoManager self.init_sensors() # Telemetry @@ -218,6 +220,14 @@ def handle_sigint(self, signal, frame): def attach_sigint_handler(self): signal.signal(signal.SIGINT, self.handle_sigint) + def handle_sigterm(self, signal, frame): + self.logger.critical("SIGTERM received") + self.shutdown() + sys.exit(0) + + def attach_sigterm_handler(self): + signal.signal(signal.SIGTERM, self.handle_sigterm) + def poll_inputs(self): self.flight_mode.poll_inputs() @@ -311,6 +321,7 @@ def run(self): self.read_command_queue_from_file() self.execute_commands() # Set goal or execute command immediately self.run_mode() + self.flight_mode.automatic_actions() finally: # TODO handle failure gracefully if FOR_FLIGHT is True: @@ -331,4 +342,4 @@ def shutdown(self): if __name__ == "__main__": FOR_FLIGHT = False main = MainSatelliteThread() - main.run() \ No newline at end of file + main.run() diff --git a/tests/commands_flight_mode_test.py b/tests/commands_flight_mode_test.py index 0a5084ff..66f15ccf 100644 --- a/tests/commands_flight_mode_test.py +++ b/tests/commands_flight_mode_test.py @@ -3,9 +3,11 @@ SafetyCommandEnum, OpNavCommandEnum, ManeuverCommandEnum, SensorsCommandEnum, CommsCommandEnum, TestCommandEnum, \ CommandCommandEnum +from flight_modes.flight_mode_factory import FLIGHT_MODE_DICT + command_enums = [BootCommandEnum, RestartCommandEnum, NormalCommandEnum, LowBatterySafetyCommandEnum, SafetyCommandEnum, OpNavCommandEnum, ManeuverCommandEnum, SensorsCommandEnum, TestCommandEnum, - CommsCommandEnum, CommandCommandEnum, TestCommandEnum, CommandCommandEnum] + CommsCommandEnum, CommandCommandEnum] def testFlightCommands(): @@ -27,11 +29,44 @@ def testFlightCommands(): assert all_enum_commands == all_command_ids except AssertionError: commands_not_in_both = set(all_enum_commands).symmetric_difference(all_command_ids) - error_msg += f"Commands defined in {enum} not consistent with utils.constants: {commands_not_in_both}\n" + error_msg += f"command_definition.py's {str(enum)[6:-1]} command dict not consistent with utils.constants: {commands_not_in_both}\n " if error_msg != "": raise AssertionError(error_msg) +def testFlightCodecs(): + flight_mode_objs = list(FLIGHT_MODE_DICT.values()) + fm_enums_zipped = list(zip(flight_mode_objs, command_enums)) + # print(fm_enums_zipped) + error_msg = "" + for fm_obj, command_enum in fm_enums_zipped: + command_enum_list = list(map(int, command_enum)) + command_codecs_list = list(fm_obj.command_codecs.keys()) + + command_enum_list = sorted(command_enum_list) + command_codecs_list = sorted(command_codecs_list) + try: + commands_not_in_both = set(command_enum_list).symmetric_difference(command_codecs_list) + assert len(commands_not_in_both) == 0 + except AssertionError: + error_msg += f"Command codec definition inconsistency. Check FM and constants: FMID: {fm_obj.flight_mode_id}, CID: {commands_not_in_both}\n" + + if error_msg != '': + raise AssertionError(error_msg) + + +def testFlightArgTypes(): + flight_mode_objs = list(FLIGHT_MODE_DICT.values()) + all_command_arg_types = [] + for fm_obj in flight_mode_objs: + all_command_arg_types.extend(list(fm_obj.command_arg_types.keys())) + all_arg_type_set = set(all_command_arg_types) + + assert len(all_command_arg_types) == len(all_arg_type_set) + + if __name__ == '__main__': testFlightCommands() + testFlightCodecs() + testFlightArgTypes() diff --git a/tests/db_test.py b/tests/db_test.py index 3587787d..58fd298d 100644 --- a/tests/db_test.py +++ b/tests/db_test.py @@ -88,6 +88,7 @@ def test_telemetry_model(): session.add(new_measurement) session.commit() + telemetry_measurements = session.query(TelemetryModel).all() assert 1 == len(telemetry_measurements) entries = session.query(TelemetryModel).all() diff --git a/tests/parameters_test.py b/tests/parameters_test.py index fee5b9ca..98eb765d 100644 --- a/tests/parameters_test.py +++ b/tests/parameters_test.py @@ -2,11 +2,14 @@ from utils.exceptions import CislunarException from json import load import os +debug = False def test_parameters(): """Copied from main.py's init_parameters""" - print(os.getcwd()) + + if debug: + print(os.getcwd()) if 'FlightSoftware/FlightSoftware/' not in os.getcwd(): # checks if running pytest filepath = 'utils/parameters.json' else: diff --git a/utils/constants.py b/utils/constants.py index b468c329..6530aa10 100644 --- a/utils/constants.py +++ b/utils/constants.py @@ -3,12 +3,12 @@ import hashlib # unit conversions - DEG2RAD = 3.14159265359 / 180 # Delay to wait on BootUp # TODO change back to 30.0 BOOTUP_SEPARATION_DELAY = 5.0 +BURNWIRE_WAITIME = 3 # hours, defined by NASA # Verification Key Parameters MAC_LENGTH = 4 @@ -60,9 +60,12 @@ NUM_BLOCKS = "num_blocks" TIME = "time" +SYS_TIME = 'sys_time' HARD_SET = "hard_set" +GOM_PIN_STATE = 'gom_pin_state' +GOM_PIN_DELAY = 'gom_pin_delay' FILE_PATH = "file_path" BLOCK_NUMBER = "block_number" @@ -95,6 +98,9 @@ DECIMATION_FACTOR = "decimation_factor" +INDEX = 'index' +VBATT = 'vbatt' + # Keyword argument definitions for downlink RTC_TIME = "rtc_time" @@ -190,11 +196,20 @@ NORM_VOLTAGE = 'norm_voltage' SAFE_VOLTAGE = 'safe_voltage' CRIT_VOLTAGE = 'crit_voltage' +OUTPUT_CHANNEL = 'output_channel' CMD = 'cmd' RETURN_CODE = 'return_code' FNAME = 'filename' +IGNORE = 'ignore' +PASSWORD = 'password' +MISSION_MODE = 'mission_mode' +NASA_PROVIDED_TIME = 'nasa_start_time' + +COMMS_OFF_START = 'comms_off_start' +COMMS_OFF_END = 'comms_off_end' + # GOMspace Channel designations: # TODO: re-evaluate and double check before flight for each satellite half @@ -268,11 +283,16 @@ class NormalCommandEnum(IntEnum): GomConf1Get = 31 GomConf2Set = 32 GomConf2Get = 33 + InitOpnav = 34 + SetCommsOffTimes = 35 ShellCommand = 50 SudoCommand = 51 Picberry = 52 ExecPyFile = 53 + MissionMode = 54 + + IgnoreLowBatt = 60 # CommandStatus = 99 @@ -327,18 +347,14 @@ class SensorsCommandEnum(IntEnum): class TestCommandEnum(IntEnum): Switch = 0 # command for switching flightmode without executing any other commands # SetTestMode = 1 # no args - TriggerBurnWire = 2 # no args - RunOpNav = 3 # no args ADCTest = 4 SeparationTest = 5 - GomPin = 6 CommsDriver = 7 RTCTest = 8 LongString = 9 PiShutdown = 11 - @unique class CommsCommandEnum(IntEnum): Switch = 0 # command for switching flightmode without executing any other commands @@ -362,3 +378,14 @@ class CommandCommandEnum(IntEnum): ActivateFile = 11 ShellCommand = 50 CeaseComms = 170 + + +@unique +class AttitudeCommandEnum(IntEnum): + Switch = 0 + + +@unique +class MissionModeEnum(IntEnum): + Boot = 0 + Normal = 1 diff --git a/utils/db.py b/utils/db.py index abcb0961..0f53d6a5 100644 --- a/utils/db.py +++ b/utils/db.py @@ -545,7 +545,7 @@ class TelemetryModel(SQLAlchemyTableBase): __tablename__ = "Telemetry" id = Column(Integer, primary_key=True) - time_polled = Column(DateTime) # FIXME + time_polled = Column(Float) # GOM DATA GOM_vboost1 = Column(Integer) diff --git a/utils/parameters.json b/utils/parameters.json index c0ca5d9d..d30071ae 100644 --- a/utils/parameters.json +++ b/utils/parameters.json @@ -27,5 +27,13 @@ "GYRO_BIAS_TEMPERATURE": 0, "GYRO_BIAS_DXDT": 0, "GYRO_BIAS_DYDT": 0, - "GYRO_BIAS_DZDT": 0 + "GYRO_BIAS_DZDT": 0, + "FOR_FLIGHT": false, + "WANT_TO_OPNAV": false, + "SPACECRAFT_NAME": "", + "CURRENT_MISSION_MODE": 0, + "BURNWIRES_FIRED": false, + "INITIAL_TIME": 1893456000, + "COMMS_OFF_TIME_START": -1, + "COMMS_OFF_TIME_END": -1 } \ No newline at end of file diff --git a/utils/parameters.py b/utils/parameters.py index b55801fb..986fd553 100644 --- a/utils/parameters.py +++ b/utils/parameters.py @@ -1,7 +1,9 @@ +FOR_FLIGHT = False +SPACECRAFT_NAME = '' DOWNLINK_BUFFER_TIME = 3 - +CURRENT_MISSION_MODE = 0 TELEM_DOWNLINK_TIME = 60 - + BOOTUP_SEPARATION_DELAY = 30.0 # seconds ENTER_LOW_BATTERY_MODE_THRESHOLD = 0.3 @@ -30,6 +32,7 @@ ACS_SPIKE_DURATION = 15 # milliseconds WANT_TO_ELECTROLYZE = False +WANT_TO_OPNAV = False DEFAULT_ELECTROLYSIS_DELAY = 1 @@ -43,3 +46,9 @@ GYRO_BIAS_DXDT = 0 GYRO_BIAS_DYDT = 0 GYRO_BIAS_DZDT = 0 + +BURNWIRES_FIRED = False +INITIAL_TIME = 1893456000 # Jan 1, 2030 + +COMMS_OFF_TIME_START = -1 +COMMS_OFF_TIME_END = -1