def Mode124_settings(self):
        """Returns settings related to Mode124.

        **Keys in returned dict:**
            'pointing_altitude': Sets in meters the altitude of the pointing command. (int) \n
            'V_offset': Used only in *Timeline_gen*. Sets the Vertical-offset angle (position in FOV) in degrees for the Moon to pass, for when the attitude freeze command is scheduled.
            Multiple values can be set but additional values will only be used when Mode124 is scheduled several times. (list of int) \n
            'H_offset': Used only in *Timeline_gen*. Sets the maximum H-offset angle from the optical axis in degrees that determines if the Moon is available. (float) \n
            'TimeToConsider': Used only in *Timeline_gen*. Sets the time in seconds for which scheduling is considered. Used to plan calibration at the start of each timeline (useful as TLE accuracy deteriorates). Drastically affects simulation time at the cost of fewer time slots being considered. (int) \n
            'timestep':  Used only in *Timeline_gen*. Sets in seconds the timestep during scheduling simulation [s]. Will impact scheduling accuracy. (int) \n
            'log_timestep': Used only in *Timeline_gen*. Sets the timestep of data being logged [s]. Only determines how much of simulated data is logged for debugging purposes. (int) \n
            'automatic':  Used only in *Timeline_gen*. Sets if the mode date is to be calculated or user provided. True for calculated or False for user provided. (bool) \n
            'start_date':  Note! only applies if *automatic* is set to False. Used only in *Timeline_gen*. Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If set to '0', Timeline_settings['start_date'] will be used. \n
            'freeze_start': Sets in seconds the time from start of the Mode to when the attitude freeze command is scheduled. Part in determining the estimated duration of the mode. (int) \n
            'freeze_duration': Sets in seconds the duration of the attitude freeze. Part in determining the estimated duration of the mode. If set to 0, it will be estimated to a
            value corresponding to the attitude being frozen until realigned with *Timeline_settings['StandardPointingAltitude']*. (int) \n
            'SnapshotTime': Sets in seconds the time, from the start of the attitude freeze, to when the first Snapshot is taken. (int) \n
            'SnapshotSpacing': Sets in seconds the time inbetween Snapshots with individual CCDs. Needs to be larger than any CCD ReadoutTimes to avoid streaks. (int) \n
            'CCDSELs': List of CCDSEL arguments (except nadir) for which to take snapshots with. (list of int)

        Returns:
            (:obj:`dict`): settings

        """
        if self.OPT_Config_File["Mode124_settings"]["freeze_duration"] == 0:
            self.OPT_Config_File["Mode124_settings"][
                "freeze_duration"] = Library.FreezeDuration_calculator(
                    self.OPT_Config_File["Timeline_settings"]
                    ["StandardPointingAltitude"],
                    self.OPT_Config_File["Mode124_settings"]
                    ["pointing_altitude"],
                    self.getTLE()[1],
                )

        return self.OPT_Config_File["Mode124_settings"]
    def Mode121_122_123_settings(self):
        """Returns settings shared between Mode121-123.

        **Keys in returned dict:**
            'pointing_altitude': Sets in meters the altitude of the pointing command. (int) \n
            'H_FOV': Used only in *Timeline_gen*. Sets full Horizontal FOV of the Limb instrument in degrees. Used to determine if stars are visible. (float) \n
            'V_FOV': Used only in *Timeline_gen*. Sets full Vertical FOV of the Limb instrument in degrees. Used to determine if stars are visible. (float) \n
            'TimeToConsider': Used only in *Timeline_gen*. Sets the time in seconds for which scheduling is considered. Used to plan calibration at the start of each timeline (useful as TLE accuracy deteriorates). Drastically affects simulation time at the cost of fewer time slots being considered. (int) \n
            'Vmag': Used only in *Timeline_gen*. Sets the Johnson V magnitude of stars to be considered (as a string expression, example '<2'). Drastically changes the runtime. \n
            'timestep': Used only in *Timeline_gen*. Set timestep used in scheduling simulation [s]. Will impact scheduling accuracy. (int) \n
            'TimeSkip': Used only in *Timeline_gen*. Set the amount of seconds to skip ahead after one complete orbit is simulated. Will drastically change the runtime of the simulation. (float) \n
            'log_timestep': Used only in *Timeline_gen*. Sets the timestep of data being logged [s]. Only determines how much of simulated data is logged for debugging purposes. (int) \n
            'freeze_start': Sets in seconds, the time from start of the Mode to when the attitude freezes. Part in determining the estimated duration of the mode. (int) \n
            'freeze_duration': Sets in seconds the duration of the attitude freeze. Part in determining the estimated duration of the mode. If set to 0, it will be estimated to a
            value corresponding to the attitude being frozen until realigned with *Timeline_settings['StandardPointingAltitude']* (Normally around 50 s). (int) \n
            'SnapshotTime': Sets in seconds the time, from the start of the attitude freeze, to when the first Snapshot is taken. (int) \n
            'SnapshotSpacing': Sets in seconds the time inbetween Snapshots with individual CCDs. Needs to be larger than any CCD ReadoutTimes to avoid streaks. (int)

        Returns:
            (:obj:`dict`): settings

        """

        if self.OPT_Config_File["Mode121_122_123_settings"][
                "freeze_duration"] == 0:
            self.OPT_Config_File["Mode121_122_123_settings"][
                "freeze_duration"] = Library.FreezeDuration_calculator(
                    self.OPT_Config_File["Timeline_settings"]
                    ["StandardPointingAltitude"],
                    self.OPT_Config_File["Mode121_122_123_settings"]
                    ["pointing_altitude"],
                    self.getTLE()[1],
                )

        return self.OPT_Config_File["Mode121_122_123_settings"]
    def Mode120_settings(self):
        """Returns settings related to Mode120.

        **Keys in returned dict:**
            'pointing_altitude': Sets in meters the altitude of the pointing command. (int) \n
            'V_offset': Used only in *Timeline_gen*. Sets the Vertical-offset angle (position in FOV) in degrees for the star to have passed, when the attitude freeze command is scheduled.
            Multiple values can be set but additional values will only be used when Mode120 is scheduled several times. (list of int) \n
            'H_offset': Used only in *Timeline_gen*. Sets the maximum Horizontal-offset angle in degrees that determines if stars are visible. (int) \n
            'Vmag': Used only in *Timeline_gen*. Sets the Johnson V magnitude of stars to be considered (as a string expression, example '<2'). Drastically changes the runtime. \n
            'TimeToConsider': Used only in *Timeline_gen*. Sets the time in seconds for which scheduling is considered. Used to plan star calibration at the start of each timeline (useful as TLE accuracy deteriorates). Drastically affects simulation time. (int) \n
            'timestep': Used only in *Timeline_gen*. Sets timestep used in scheduling simulation [s]. Will impact scheduling accuracy. (int) \n
            'TimeSkip': Used only in *Timeline_gen*. Set the amount of seconds to skip ahead after one complete orbit is simulated. Will drastically change the runtime of the simulation. (int) \n
            'log_timestep': Used only in *Timeline_gen*. Sets the timestep of data being logged [s]. Only determines how much of simulated data is logged for debugging purposes.. (int) \n
            'automatic': Used only in *Timeline_gen*. Sets if 'start_date' will be calculated or user provided. True for calculated and False for user provided. (bool) \n
            'start_date':  Note! only applies if *automatic* is set to False. Used only in *Timeline_gen*. Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If set to '0', Timeline_settings['start_date'] will be used. \n
            'freeze_start': Sets in seconds the time from start of the Mode to when the attitude freezes. Part in determining the estimated duration of the mode. (int) \n
            'freeze_duration': Sets in seconds the duration of the attitude freeze. Part in determining the estimated duration of the mode. If set to 0, it will be estimated to a
            value corresponding to the attitude being frozen until realigned with *Timeline_settings['StandardPointingAltitude']* (Normally around 50 s). (int) \n
            'SnapshotTime': Sets in seconds the time, from the start of the attitude freeze, to when the first Snapshot is taken. (int) \n
            'SnapshotSpacing': Sets in seconds the time inbetween Snapshots with individual CCDs. (int) \n
            'CCDSELs': List of CCDSEL arguments (except nadir) for which to take snapshots with. (list of int)

        Returns:
            (:obj:`dict`): settings

        """
        if self.OPT_Config_File["Mode120_settings"]["freeze_duration"] == 0:
            self.OPT_Config_File["Mode120_settings"][
                "freeze_duration"] = Library.FreezeDuration_calculator(
                    self.OPT_Config_File["Timeline_settings"]
                    ["StandardPointingAltitude"],
                    self.OPT_Config_File["Mode120_settings"]
                    ["pointing_altitude"],
                    self.getTLE()[1],
                )

        return self.OPT_Config_File["Mode120_settings"]
class configFile:

    Library.SetupLogger("configFile")

    def __init__(
        self,
        config_file_name,
        date="2020/6/20 18:32:42",
        TLE1="1 54321U 19100G   20172.75043981 0.00000000  00000-0  75180-4 0  0014",
        TLE2="2 54321  97.7044   6.9210 0014595 313.2372  91.8750 14.93194142000010",
    ):

        self.config_file_name = config_file_name
        self.TLE1 = TLE1
        self.TLE2 = TLE2
        self.date = date

        with open(self.config_file_name) as json_file:
            OPT_Config_File = json.load(json_file)

        self.OPT_Config_File = OPT_Config_File

        self.OPT_Config_File["Timeline_settings"]["duration"]["duration"] = (
            self.OPT_Config_File["Timeline_settings"]["duration"]["day"] * 24 *
            3600 +
            self.OPT_Config_File["Timeline_settings"]["duration"]["hours"] *
            3600 +
            self.OPT_Config_File["Timeline_settings"]["duration"]["seconds"])

        self.OPT_Config_File["Mode120_settings"]["TimeToConsider"][
            "TimeToConsider"] = (self.OPT_Config_File["Mode120_settings"]
                                 ["TimeToConsider"]["hours"] * 3600 +
                                 self.OPT_Config_File["Mode120_settings"]
                                 ["TimeToConsider"]["seconds"])

        self.OPT_Config_File["Mode120_settings"]["TimeSkip"]["TimeSkip"] = (
            self.OPT_Config_File["Mode120_settings"]["TimeSkip"]["hours"] *
            3600 +
            self.OPT_Config_File["Mode120_settings"]["TimeSkip"]["seconds"])

        self.OPT_Config_File["Mode121_122_123_settings"]["TimeToConsider"][
            "TimeToConsider"] = (
                self.OPT_Config_File["Mode121_122_123_settings"]
                ["TimeToConsider"]["hours"] * 3600 +
                self.OPT_Config_File["Mode121_122_123_settings"]
                ["TimeToConsider"]["seconds"])

        self.OPT_Config_File["Mode121_122_123_settings"]["TimeSkip"][
            "TimeSkip"] = (self.OPT_Config_File["Mode121_122_123_settings"]
                           ["TimeSkip"]["hours"] * 3600 +
                           self.OPT_Config_File["Mode121_122_123_settings"]
                           ["TimeSkip"]["seconds"])

        self.OPT_Config_File["Mode124_settings"]["TimeToConsider"][
            "TimeToConsider"] = (self.OPT_Config_File["Mode124_settings"]
                                 ["TimeToConsider"]["hours"] * 3600 +
                                 self.OPT_Config_File["Mode124_settings"]
                                 ["TimeToConsider"]["seconds"])

        self.current_pointing = None
        self.latestRelativeTime = 0
        self.LargestSetTEXPMS = 0
        self.Mode120Iteration = 1
        self.Mode124Iteration = 1

    def Logger_name(self):
        """Returns the name of the shared logger.

        Returns:
            (str): Logger_name

        """

        return self.OPT_Config_File["Logger_name"]

    def Version(self):
        """'Returns the version ID of this Configuration File.

        The version ID should only be changed when the default *Configuration File*, _ConfigFile in OPT, is changed.

        Returns:
            (str): version_name

        """

        return self.OPT_Config_File["version_ID"]

    def Scheduling_priority(self):
        """Returns the Modes (except *Operational Science Modes* (Mode 1,2,5)) and StartUpCMDs planned to be schedueled in a *Science Mode Timeline* using *Timeline_gen*.

        **Available choices are:** \n

            - 'ArgEnableYawComp',
            - 'Payload_Power_Toggle',
            - 'TurnONCCDs',
            - 'CCDFlushBadColumns',
            - 'CCDBadColumn',
            - 'PM',
            - 'HTR',
            - 'CCDBIAS',
            - 'Mode100',
            - 'Mode110',
            - 'Mode120',
            - 'Mode121',
            - 'Mode122',
            - 'Mode123',
            - 'Mode124',
            - 'Mode131',
            - 'Mode132',
            - 'Mode133',
            - 'Mode130',
            - 'Mode134'

        The order of which the Modes/StartUpCMDs appear is also their priority order (top-down). \n
        Repeat Modes/CMDs to schedule them several times, though there is currently not feature that allows multiple Modes/CMDs
        to be scheduled with different settings. Some settings that does not affect the duration of the Mode can still be changed manually afterwards in the generated Science Mode Timeline.
        Each string must be equal to the name of a function imported in the *_Timeline_generator.Modes.Modes_Header* module. \n

        'Payload_Power_Toggle', 'TurnONCCDs', 'ArgEnableYawComp', 'CCDFlushBadColumns', 'CCDBadColumn', 'PM', 'CCDBIAS' are called StartUpCMDs and are recommended to run at the start of each
        timeline, as they together reset and initialize the intrument. If "HTR" is scheduled, arguments is needed to be entered manually into the created *Science Mode Timeline* because of safety reasons. \n

        'Payload_Power_Toggle' is a OHB procedure that safely power toggles the instrument. \n

        'TurnONCCDs' is a special CCD macro that turns on the CCDs.

        Returns:
            (:obj:`list` of :obj:`str`): Modes_priority

        """

        return self.OPT_Config_File["Modes_priority"]

    def getTLE(self):
        """Returns the TLE as two strings in a list.

        Returns a TLE from the *Globals* module if *Set_ConfigFile* has been ran with a TLE as input.
        Otherwise returns any TLE values stated here.

        Returns:
            (:obj:`list` of :obj:`str`): First Element is the first TLE row, and the second Element is the second row.

        """

        if (not self.TLE1 == ("")) and (not self.TLE2 == ("")):
            TLE1 = self.OPT_Config_File["TLE1"]
            TLE2 = self.OPT_Config_File["TLE2"]
        else:
            "If no TLE has been chosen with *Set_ConfigFile*, these values are used instead."
            TLE1 = (
                "1 54321U 19100G   20172.75043981 0.00000000  00000-0  75180-4 0  0014"
            )
            TLE2 = (
                "2 54321  97.7044   6.9210 0014595 313.2372  91.8750 14.93194142000010"
            )

        return [TLE1, TLE2]

    def Timeline_settings(self):
        """Returns settings related to a *Science Mode Timeline* as a whole.

        **Keys:**
            'start_date': Sets the starting date of the timeline (str), (example: '2018/9/3 08:00:40'). Contains Globals.StartTime if *Set_ConfigFile* has been ran with a start date as an input. Otherwise any value stated here.  \n
            'duration': Sets the duration [s] of the timeline. Will drastically change the runtime of *Timeline_gen*. A runtime of around 15 min is estimated for a duration of 1 week (int) \n

            'Mode1_2_5_minDuration': Minimum amount of available time needed, inbetween scheduled Modes/CMDs in a *Science Mode Timeline*, to allow the scheduling of *Operational Science Modes* when running *Timeline_gen* [s]. \n
            'mode_separation': Time in seconds for an added buffer when determining the duration of a Mode/CMD. (int) \n
            'CMD_duration': Sets the amount of time scheduled for separate PayloadCMDs when using *Timeline_gen*. Should be large enough to allow any separate CMD to finish processing.  (int) \n

            'yaw_correction': Determines if yaw correction shall be used for the duration of the timeline. Mainly impacts simulations of MATS's pointing. But also determines the argument of the ArgEnableYawComp CMD. (bool) \n
            'yaw_amplitude': Amplitude of the yaw function, A*cos(argument_of_latitude - B - phase), where B is angle between optical axis and negative velocity vector in the orbital plane. (float) \n
            'yaw_phase': Phase of the yaw function, A*cos(argument_of_latitude - B - phase), where B is angle between optical axis and negative velocity vector in the orbital plane. (float) \n
            'Choose_Operational_Science_Mode': Set to 1, 2, or 5 to choose either Mode1, Mode2, or Mode5 as the *Operational Science Mode*. Set to 0 to schedule either Mode1 or Mode2 depending of the time of the year.
            'StandardPointingAltitude': Sets pointing altitude in meters for the timeline. Used to set the pointing altitude of *Operational Science Modes* and to calculate the duration of attitude freezes (because attitude freezes last until the pointing altitude is reorientated to this value).  (int) \n

            'CMD_separation': Changes the separation in time [s] between commands that are scheduled in *XML_gen*. If set too large, it is possible that not enough time is scheduled for the duration of Modes, causing Modes to overlap in time. Impacts the estimated duration of certain Science Modes in *Timeline_gen*. (float) \n
            'pointing_stabilization': The maximum time it takes for an attitude change to stabilize [s]. Used before scheduling certain CMDs in *XML_gen* to make sure that the attitude has been stabilized after running *TC_acfLimbPointingAltitudeOffset*. Impacts the estimated duration of Science Modes in *Timeline_gen*. (int) \n
            'CCDSYNC_ExtraOffset': Extra offset time [ms] that is added to an estimated ReadoutTime when calculating TEXPIOFS for the CCD Synchronize CMD. (int) \n
            'CCDSYNC_ExtraIntervalTime': Extra time [ms] that is added to the calculated Exposure Interval Time (for example when calculating arguments for the CCD Synchronize CMD or nadir TEXPIMS). (int) \n

        Returns:
            (:obj:`dict`): Timeline_settings
        """
        return self.OPT_Config_File["Timeline_settings"]

    def Operational_Science_Mode_settings(self):
        """Returns settings related to Operational Science Modes (Mode1, 2, and 5).

        **Keys:**
            'lat': Applies only to Mode1! Sets in degrees the latitude (+ and -) that the LP crosses that causes the UV exposure to swith on/off. (int) \n
            'log_timestep': Used only in *XML_gen*. Sets the frequency of data being logged [s] for Mode1-2. Only determines how much of simulated data is logged for debugging purposes. (int) \n
            'timestep': Sets the timestep [s] of the XML generator simulation of Mode1-2. Will impact accuracy of command generation but also drastically changes the runtime of XML-gen. (int) \n
            'Choose_Mode5CCDMacro': Applies only to Mode5! Sets the CCD macro to be used by Mode5. Used as input to *CCD_macro_settings* in the ConfigFile (str).

        Returns:
            (:obj:`dict`): settings

        """

        return self.OPT_Config_File["Operational_Science_Mode_settings"]

    """
    def Mode5_settings():
        '''Returns settings related to Mode5.

        **Keys in returned dict:**
            'pointing_altitude': Sets in meters the altitude of the pointing command. If set to 0, Timeline_settings['StandardPointingAltitude'] will be used (int)

        Returns:
            (:obj:`dict`): settings

        '''

        settings = {'pointing_altitude': 110000}

        return settings

    """

    def Mode100_settings(self):
        """Returns settings related to Mode100.

        **Keys in returned dict:**
            'pointing_altitude_from': Sets in meters the starting altitude. Part in determining the estimated duration of the mode. (int) \n
            'pointing_altitude_to': Sets in meters the ending altitude. Part in determining the estimated duration of the mode. (int) \n
            'pointing_altitude_interval': Sets in meters the interval size of each succesive pointing. Part in determining the estimated duration of the mode. (int) \n
            'pointing_duration': Sets the time [s] from attitude stabilization until next pointing command. Part in determining the estimated duration of the mode. (int) \n
            'Exp_Time_IR': Sets starting exposure time [ms] as a integer. \n
            'Exp_Time_UV': Sets starting exposure time [ms] as a integer. \n
            'ExpTime_step': Sets in ms the interval size of both ExpTimeUV and ExpTimeIR for each succesive pointing. (int) \n
            'start_date': Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If the date is set to '0', Timeline_settings['start_date'] will be used.

        Returns:
            (:obj:`dict`): settings

        """

        return self.OPT_Config_File["Mode100_settings"]

    def Mode110_settings(self):
        """Returns settings related to Mode110.

        **Keys in returned dict:**
            'pointing_altitude_from': Sets in meters the starting altitude of the sweep. Part in determining the estimated duration of the mode. (int) \n
            'pointing_altitude_to': Sets in meters the ending altitude of the sweep. Part in determining the estimated duration of the mode. (int) \n
            'sweep_rate': Sets the rate of the sweep in m/s. Part in determining the estimated duration of the mode. (int) \n
            'start_date': Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If the date is set to '0', Timeline_settings['start_date'] will be used.
            'Exp_Time_IR': Sets exposure time [ms] of the IR CCDs. (int) \n
            'Exp_Time_UV': Sets exposure time [ms] of the UV CCDs. (int) \n

        Returns:
            (:obj:`dict`): settings

        """

        return self.OPT_Config_File["Mode110_settings"]

    def Mode120_settings(self):
        """Returns settings related to Mode120.

        **Keys in returned dict:**
            'pointing_altitude': Sets in meters the altitude of the pointing command. (int) \n
            'V_offset': Used only in *Timeline_gen*. Sets the Vertical-offset angle (position in FOV) in degrees for the star to have passed, when the attitude freeze command is scheduled.
            Multiple values can be set but additional values will only be used when Mode120 is scheduled several times. (list of int) \n
            'H_offset': Used only in *Timeline_gen*. Sets the maximum Horizontal-offset angle in degrees that determines if stars are visible. (int) \n
            'Vmag': Used only in *Timeline_gen*. Sets the Johnson V magnitude of stars to be considered (as a string expression, example '<2'). Drastically changes the runtime. \n
            'TimeToConsider': Used only in *Timeline_gen*. Sets the time in seconds for which scheduling is considered. Used to plan star calibration at the start of each timeline (useful as TLE accuracy deteriorates). Drastically affects simulation time. (int) \n
            'timestep': Used only in *Timeline_gen*. Sets timestep used in scheduling simulation [s]. Will impact scheduling accuracy. (int) \n
            'TimeSkip': Used only in *Timeline_gen*. Set the amount of seconds to skip ahead after one complete orbit is simulated. Will drastically change the runtime of the simulation. (int) \n
            'log_timestep': Used only in *Timeline_gen*. Sets the timestep of data being logged [s]. Only determines how much of simulated data is logged for debugging purposes.. (int) \n
            'automatic': Used only in *Timeline_gen*. Sets if 'start_date' will be calculated or user provided. True for calculated and False for user provided. (bool) \n
            'start_date':  Note! only applies if *automatic* is set to False. Used only in *Timeline_gen*. Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If set to '0', Timeline_settings['start_date'] will be used. \n
            'freeze_start': Sets in seconds the time from start of the Mode to when the attitude freezes. Part in determining the estimated duration of the mode. (int) \n
            'freeze_duration': Sets in seconds the duration of the attitude freeze. Part in determining the estimated duration of the mode. If set to 0, it will be estimated to a
            value corresponding to the attitude being frozen until realigned with *Timeline_settings['StandardPointingAltitude']* (Normally around 50 s). (int) \n
            'SnapshotTime': Sets in seconds the time, from the start of the attitude freeze, to when the first Snapshot is taken. (int) \n
            'SnapshotSpacing': Sets in seconds the time inbetween Snapshots with individual CCDs. (int) \n
            'CCDSELs': List of CCDSEL arguments (except nadir) for which to take snapshots with. (list of int)

        Returns:
            (:obj:`dict`): settings

        """
        if self.OPT_Config_File["Mode120_settings"]["freeze_duration"] == 0:
            self.OPT_Config_File["Mode120_settings"][
                "freeze_duration"] = Library.FreezeDuration_calculator(
                    self.OPT_Config_File["Timeline_settings"]
                    ["StandardPointingAltitude"],
                    self.OPT_Config_File["Mode120_settings"]
                    ["pointing_altitude"],
                    self.getTLE()[1],
                )

        return self.OPT_Config_File["Mode120_settings"]

    def Mode121_122_123_settings(self):
        """Returns settings shared between Mode121-123.

        **Keys in returned dict:**
            'pointing_altitude': Sets in meters the altitude of the pointing command. (int) \n
            'H_FOV': Used only in *Timeline_gen*. Sets full Horizontal FOV of the Limb instrument in degrees. Used to determine if stars are visible. (float) \n
            'V_FOV': Used only in *Timeline_gen*. Sets full Vertical FOV of the Limb instrument in degrees. Used to determine if stars are visible. (float) \n
            'TimeToConsider': Used only in *Timeline_gen*. Sets the time in seconds for which scheduling is considered. Used to plan calibration at the start of each timeline (useful as TLE accuracy deteriorates). Drastically affects simulation time at the cost of fewer time slots being considered. (int) \n
            'Vmag': Used only in *Timeline_gen*. Sets the Johnson V magnitude of stars to be considered (as a string expression, example '<2'). Drastically changes the runtime. \n
            'timestep': Used only in *Timeline_gen*. Set timestep used in scheduling simulation [s]. Will impact scheduling accuracy. (int) \n
            'TimeSkip': Used only in *Timeline_gen*. Set the amount of seconds to skip ahead after one complete orbit is simulated. Will drastically change the runtime of the simulation. (float) \n
            'log_timestep': Used only in *Timeline_gen*. Sets the timestep of data being logged [s]. Only determines how much of simulated data is logged for debugging purposes. (int) \n
            'freeze_start': Sets in seconds, the time from start of the Mode to when the attitude freezes. Part in determining the estimated duration of the mode. (int) \n
            'freeze_duration': Sets in seconds the duration of the attitude freeze. Part in determining the estimated duration of the mode. If set to 0, it will be estimated to a
            value corresponding to the attitude being frozen until realigned with *Timeline_settings['StandardPointingAltitude']* (Normally around 50 s). (int) \n
            'SnapshotTime': Sets in seconds the time, from the start of the attitude freeze, to when the first Snapshot is taken. (int) \n
            'SnapshotSpacing': Sets in seconds the time inbetween Snapshots with individual CCDs. Needs to be larger than any CCD ReadoutTimes to avoid streaks. (int)

        Returns:
            (:obj:`dict`): settings

        """

        if self.OPT_Config_File["Mode121_122_123_settings"][
                "freeze_duration"] == 0:
            self.OPT_Config_File["Mode121_122_123_settings"][
                "freeze_duration"] = Library.FreezeDuration_calculator(
                    self.OPT_Config_File["Timeline_settings"]
                    ["StandardPointingAltitude"],
                    self.OPT_Config_File["Mode121_122_123_settings"]
                    ["pointing_altitude"],
                    self.getTLE()[1],
                )

        return self.OPT_Config_File["Mode121_122_123_settings"]

    def Mode121_settings(self):
        """Returns settings related to Mode121.

        **Keys in returned dict:**
            'start_date':  Note! only applies if *automatic* is set to False. Used only in *Timeline_gen*. Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If set to '0', Timeline_settings['start_date'] will be used. \n
            'automatic': Used only in *Timeline_gen*. Sets if 'start_date' will be calculated or user provided. True for calculated and False for user provided. (bool) \n
            'CCDSELs': List of CCDSEL arguments (except nadir) for which to take snapshots with. (list of int)

        Returns:
            (:obj:`dict`): settings
        """

        Settings = self.OPT_Config_File["Mode121_settings"]
        CommonSettings = self.Mode121_122_123_settings()

        settings = {**CommonSettings, **Settings}

        return settings

    def Mode122_settings(self):
        """Returns settings related to Mode122.

        **Keys in returned dict:**
            'start_date':  Note! only applies if *automatic* is set to False. Used only in *Timeline_gen*. Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If set to '0', Timeline_settings['start_date'] will be used. \n
            'automatic': Used only in *Timeline_gen*. Sets if 'start_date' will be calculated or user provided. True for calculated and False for user provided. (bool) \n
            'Exp_Time_IR': Sets exposure time [ms] of the IR CCDs. (int) \n
            'Exp_Time_UV': Sets exposure time [ms] of the UV CCDs. (int) \n

        Returns:
            (:obj:`dict`): settings
        """

        Settings = self.OPT_Config_File["Mode122_settings"]
        CommonSettings = self.Mode121_122_123_settings()

        settings = {**CommonSettings, **Settings}

        return settings

    def Mode123_settings(self):
        """Returns settings related to Mode123.

        **Keys in returned dict:**
            'start_date':  Note! only applies if *automatic* is set to False. Used only in *Timeline_gen*. Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If set to '0', Timeline_settings['start_date'] will be used. \n
            'automatic': Used only in *Timeline_gen*. Sets if 'start_date' will be calculated or user provided. True for calculated and False for user provided. (bool) \n
            'Exp_Time_IR': Sets exposure time [ms] of the IR CCDs. (int) \n
            'Exp_Time_UV': Sets exposure time [ms] of the UV CCDs. (int) \n

        Returns:
            (:obj:`dict`): settings
        """

        Settings = self.OPT_Config_File["Mode123_settings"]
        CommonSettings = self.Mode121_122_123_settings()

        settings = {**CommonSettings, **Settings}

        return settings

    def Mode124_settings(self):
        """Returns settings related to Mode124.

        **Keys in returned dict:**
            'pointing_altitude': Sets in meters the altitude of the pointing command. (int) \n
            'V_offset': Used only in *Timeline_gen*. Sets the Vertical-offset angle (position in FOV) in degrees for the Moon to pass, for when the attitude freeze command is scheduled.
            Multiple values can be set but additional values will only be used when Mode124 is scheduled several times. (list of int) \n
            'H_offset': Used only in *Timeline_gen*. Sets the maximum H-offset angle from the optical axis in degrees that determines if the Moon is available. (float) \n
            'TimeToConsider': Used only in *Timeline_gen*. Sets the time in seconds for which scheduling is considered. Used to plan calibration at the start of each timeline (useful as TLE accuracy deteriorates). Drastically affects simulation time at the cost of fewer time slots being considered. (int) \n
            'timestep':  Used only in *Timeline_gen*. Sets in seconds the timestep during scheduling simulation [s]. Will impact scheduling accuracy. (int) \n
            'log_timestep': Used only in *Timeline_gen*. Sets the timestep of data being logged [s]. Only determines how much of simulated data is logged for debugging purposes. (int) \n
            'automatic':  Used only in *Timeline_gen*. Sets if the mode date is to be calculated or user provided. True for calculated or False for user provided. (bool) \n
            'start_date':  Note! only applies if *automatic* is set to False. Used only in *Timeline_gen*. Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If set to '0', Timeline_settings['start_date'] will be used. \n
            'freeze_start': Sets in seconds the time from start of the Mode to when the attitude freeze command is scheduled. Part in determining the estimated duration of the mode. (int) \n
            'freeze_duration': Sets in seconds the duration of the attitude freeze. Part in determining the estimated duration of the mode. If set to 0, it will be estimated to a
            value corresponding to the attitude being frozen until realigned with *Timeline_settings['StandardPointingAltitude']*. (int) \n
            'SnapshotTime': Sets in seconds the time, from the start of the attitude freeze, to when the first Snapshot is taken. (int) \n
            'SnapshotSpacing': Sets in seconds the time inbetween Snapshots with individual CCDs. Needs to be larger than any CCD ReadoutTimes to avoid streaks. (int) \n
            'CCDSELs': List of CCDSEL arguments (except nadir) for which to take snapshots with. (list of int)

        Returns:
            (:obj:`dict`): settings

        """
        if self.OPT_Config_File["Mode124_settings"]["freeze_duration"] == 0:
            self.OPT_Config_File["Mode124_settings"][
                "freeze_duration"] = Library.FreezeDuration_calculator(
                    self.OPT_Config_File["Timeline_settings"]
                    ["StandardPointingAltitude"],
                    self.OPT_Config_File["Mode124_settings"]
                    ["pointing_altitude"],
                    self.getTLE()[1],
                )

        return self.OPT_Config_File["Mode124_settings"]

    def Mode130_settings(self):
        """Returns settings related to Mode130.

        **Keys in returned dict:**
            'pointing_altitude': Sets in meters the altitude of the pointing command. (int) \n
            'SnapshotSpacing': Sets in seconds the time inbetween Snapshots with individual CCDs. Part in determining the estimated duration of the mode. (int) \n
            'start_date': Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If the date is set to '0', Timeline_settings['start_date'] will be used.

        Returns:
            (:obj:`dict`): settings

        """

        return self.OPT_Config_File["Mode130_settings"]

    def Mode131_settings(self):
        """Returns settings related to Mode131.

        **Keys in returned dict:**
            'pointing_altitude': Sets in meters the altitude of the pointing command. (int) \n
            'mode_duration': Sets the scheduled duration of the Mode in seconds. Must be long enough to allow any pointing stabilization, CCD synchronization (takes 1 TEXPIMS cycle to execute), and execution of CMDs to occur. (int) \n
            'start_date': Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If the date is set to '0', Timeline_settings['start_date'] will be used.

        Returns:
            (:obj:`dict`): settings

        """

        return self.OPT_Config_File["Mode131_settings"]

    def Mode132_settings(self):
        """Returns settings related to Mode132.

        **Keys in returned dict:**
            'pointing_altitude': Sets in meters the altitude of the pointing command. (int) \n
            'start_date': Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If the date is set to '0', Timeline_settings['start_date'] will be used. \n
            'Exp_Times_IR': Sets exposure times [ms] as a list of integers. Part in determining the estimated duration of the mode. Shall have equal length to 'Exp_Times_UV'.  \n
            'Exp_Times_UV': Sets exposure times [ms] as a list of integers. Part in determining the estimated duration of the mode. Shall have equal length to 'Exp_Times_IR'. \n
            'session_duration': Sets the duration [s] of each session spent in operational mode using the different exposure times in *Exp_Times*. Part in determining the estimated duration of the mode. (int)

        Returns:
            (:obj:`dict`): settings

        """

        return self.OPT_Config_File["Mode132_settings"]

    def Mode133_settings(self):
        """Returns settings related to Mode133.

        **Keys in returned dict:**
            'pointing_altitude': Sets in meters the altitude of the pointing command. (int) \n
            'start_date': Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If the date is set to '0', Timeline_settings['start_date'] will be used. \n
            'Exp_Times_IR': Sets exposure times [ms] as a list of integers. Part in determining the estimated duration of the mode. Shall have equal length to 'Exp_Times_UV'.  \n
            'Exp_Times_UV': Sets exposure times [ms] as a list of integers. Part in determining the estimated duration of the mode. Shall have equal length to 'Exp_Times_IR'. \n
            'session_duration': Sets the duration [s] of each session spent in operational mode using the different exposure times in *Exp_Times_UV* and *Exp_Times_IR*. Part in determining the estimated duration of the mode. (int)

        Returns:
            (:obj:`dict`): settings

        """

        return self.OPT_Config_File["Mode133_settings"]

    def Mode134_settings(self):
        """Returns settings related to Mode134.

        **Keys in returned dict:**
            'pointing_altitude': Sets in meters the altitude of the pointing command. (int) \n
            'mode_duration': Sets the scheduled duration of the Mode in seconds. Must be long enough to allow any pointing stabilization, CCD synchronization (takes 1 TEXPIMS cycle to execute), and execution of CMDs to occur. (int) \n
            'start_date': Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If the date is set to '0', Timeline_settings['start_date'] will be used.

        Returns:
            (:obj:`dict`): settings

        """

        return self.OPT_Config_File["Mode134_settings"]

    """
    def Mode201_settings():
        '''Returns settings related to Mode201.

        **Keys in returned dict:**
            'pointing_altitude': Sets in meters the altitude of the pointing command. \n
            'mode_duration': Sets the scheduled duration of the Mode in seconds. \n
            'start_date': Sets the scheduled date for the mode as a str, (example: '2018/9/3 08:00:40'). If the date is set to '0', Timeline_settings['start_date'] will be used.

        Returns:
            (:obj:`dict`): settings

        '''
        settings = {'pointing_altitude': 70000, 'mode_duration': 600, 'start_date': '0'}
        return settings



    def Mode203_settings():
        '''Returns settings related to Mode203.

        **Keys in returned dict:**
            'pitch': Sets the pitch axis maneuver.

        Returns:
            settings (:obj:`dict`)

        '''
        settings = {'pitch': 180}
        return settings
    """

    def PWRTOGGLE_settings(self):
        """Returns settings related to the PWRTOGGLE CMD.

        **Keys in returned dict:**
            'CONST': Magic Constant (int).

        Returns:
            (:obj:`dict`): settings

        """
        return self.OPT_Config_File["PWRTOGGLE_settings"]

    def CCDFlushBadColumns_settings(self):
        """Returns settings related to the CCDFlushBadColumns CMD.

        **Keys in returned dict:**
            'CCDSEL': CCD select, 1 bit for each CCD (1..127). (int)

        Returns:
            (:obj:`dict`): settings

        """
        return self.OPT_Config_File["CCDFlushBadColumns_settings"]

    def CCDBadColumn_settings(self):
        """Returns settings related to CCDBadColumn CMD.

        **Keys in returned dict:**
            'CCDSEL': CCD select, 1 bit for each CCD (1..127). (int) \n
            'NBC': Number of uint16 in BC as a uint16. Big Endian. Maximum number is 63. (int) \n
            'BC': Bad Columns as a list of uint16 (4..2047).

        Returns:
            (:obj:`dict`): settings

        """
        return self.OPT_Config_File["CCDBadColumn_settings"]

    def PM_settings(self):
        """Returns settings related to the PM (photometer) CMD.

        **Keys in returned dict:**
            'TEXPMS': Exposure time [ms] for the photometer (int) \n
            'TEXPIMS': Exposure interval time [ms] for the photometer (int)

        Returns:
            (:obj:`dict`): settings

        """
        return self.OPT_Config_File["PM_settings"]

    def CCDBIAS_settings(self):
        """Returns settings related to the CCDBIAS CMD.

        **Keys in returned dict:**
            'CCDSEL': CCD select, 1 bit for each CCD (1..127). (int) \n
            'VGATE': 8-bit value representing a Voltage (int) \n
            'VSUBST': 8-bit value representing a Voltage (int) \n
            'VRD': 8-bit value representing a Voltage (int) \n
            'VOD': 8-bit value representing a Voltage (int) \n

        Returns:
            (:obj:`dict`): settings

        """
        return self.OPT_Config_File["CCDBIAS_settings"]

    """
    def HTR_settings():
        '''Returns settings related to the HTR CMD.

        **Keys in returned dict:**
            'HTRSEL': HTR select, 1 bit for each HTR (bits 0,1,6,7). (int) \n
            'SET': Heater set point (int) \n
            'PVALUE': Regulator Proportional constant (int) \n
            'IVALUE': Regulator Integral constant (int) \n
            'DVALUE': Regulator Derivative constant (int) \n

        Returns:
            (:obj:`dict`): settings

        '''
        settings = {'CCDSEL': 3, 'SET': 1402, 'PVALUE': 256, 'IVALUE': 10, 'DVALUE': 0}
        return settings
    """

    def CCD_macro_settings(self, CCDMacroSelect):
        """Returns CCD settings for a specific CCD macro.

        Each key in the output represents settings for a corresponding CCDSEL argument.
        TEXPIMS for the CCDs (except nadir) is not changeable as they need to be synchronized with a calculated TEXPIMS to prevent streaks. This is usually performed when macros are scheduled.

        **Keys in returned dict:**
            16: Represents settings for UV1 (CCD5) \n
            32: Represents settings for UV2 (CCD6) \n
            1: Represents settings for IR1 (CCD1) \n
            8: Represents settings for IR2 (CCD4) \n
            2: Represents settings for IR4 (CCD2) \n
            4: Represents settings for IR3 (CCD3) \n
            64: Represents settings for Nadir (CCD6) \n


        Arguments:
            CCDMacroSelect (str): Specifies for which CCD macro, settings are returned for. 'CustomBinning', 'HighResUV', 'HighResIR', 'BinnedCalibration', 'FullReadout', 'LowPixel'.

        Returns:
            (:obj:`dict`): CCD_settings

        """

        CCD_settings = {16: {}, 32: {}, 1: {}, 8: {}, 2: {}, 4: {}, 64: {}}
        CCD_settings[16] = self.OPT_Config_File["CCD_macro_settings"][
            CCDMacroSelect]["CCD_settings_UV1"]
        CCD_settings[32] = self.OPT_Config_File["CCD_macro_settings"][
            CCDMacroSelect]["CCD_settings_UV2"]
        CCD_settings[1] = self.OPT_Config_File["CCD_macro_settings"][
            CCDMacroSelect]["CCD_settings_IR1"]
        CCD_settings[8] = self.OPT_Config_File["CCD_macro_settings"][
            CCDMacroSelect]["CCD_settings_IR2"]
        CCD_settings[2] = self.OPT_Config_File["CCD_macro_settings"][
            CCDMacroSelect]["CCD_settings_IR3"]
        CCD_settings[4] = self.OPT_Config_File["CCD_macro_settings"][
            CCDMacroSelect]["CCD_settings_IR4"]
        CCD_settings[64] = self.OPT_Config_File["CCD_macro_settings"][
            CCDMacroSelect]["CCD_settings_Nadir"]

        return CCD_settings

    def CheckConfigFile(self):
        """Checks the values of the settings in the *Configuration File* chosen with *Set_ConfigFile*.

        Also prints out the currently selected *Configuration File* and which starting date and TLE it currently uses.

        """
        from mats_planningtool.CheckConfigFile.Core import CheckConfigFile

        CheckConfigFile(self)

    def Timeline_gen(self):
        """Invokes the Timeline generator part of Operational Planning Tool.

        Creates a *Science Mode Timeline* as a .json file. \n
        Predicts and schedueles Science Modes that depend on certain events such as position of stars and the moon (Mode120-Mode124).
        Other Science Modes and StartUpCMDs are just scheduled at the start of the Timeline or at a given date.
        The Science Modes and StartUpCMDs to be scheduled are listed in *Modes_priority* in the chosen *Configuration File*.

        *Operational Science Modes* (example: Mode 1,2,5) are scheduled separately wherever time is available at the end of the program.

        Settings for the operation of the program are stated in the *Configuration File* chosen with *Set_ConfigFile*.

        Returns:
            None
        """
        from mats_planningtool.TimelineGenerator.Core import Timeline_generator

        Timeline_generator(self)

    def XML_gen(self, SCIMOD_Path=None):
        """Invokes the XML generator program part of Operational Planning Tool for MATS.

        Converts a *Science Mode Timeline*  (.json file) containing a list of scheduled Science Modes/CMDs/Tests into Payload and Platform commands and saves them as a .xml command file.  \n
        Settings for the operation of the program are stated in the chosen *Configuration File*, set by *Set_ConfigFile*.
        Settings given in the *Science Mode Timeline* override the settings given in the chosen *Configuration file* or set with *Set_ConfigFile*.

        Arguments:
            science_mode_timeline_path (str): Path to the .json file containing the Science Mode Timeline.

        Returns:
            None
        """

        from .XMLGenerator.XML_gen import XML_generator

        "Initialize current_pointing to None"
        self.current_pointing = None

        if SCIMOD_Path == None:
            SCIMOD_Path = os.path.join(
                "Output",
                "Science_Mode_Timeline_" +
                os.path.split(self.config_file_name)[1],
            )
        XML_TIMELINE = XML_generator(self, SCIMOD_Path)

        return XML_TIMELINE

    def Plot_Timeline_Plotter_Plots(
        self,
        FigureDirectory,
        FilesToPlot=[
            "ActiveScienceMode",
            "Yaw",
            "Pitch",
            "Roll",
            "Lat",
            "Long",
            "Alt",
            "ECEFerror",
            "PosError",
            "PosErrorRCI",
            "MagPosError",
            "Lat_LP",
            "Long_LP",
            "Alt_LP",
            "AltError_LP",
            "PosError_LP",
            "PosErrorRCI_LP",
            "MagPosError_LP",
            "RA_OpticalAxis",
            "RA_OpticalAxisError",
            "Dec_OpticalAxis",
            "Dec_OpticalAxisError",
            "PosErrorMATS_STK",
        ],
    ):
        """Plots binary files created with *Timeline_Plotter*.

        Tries to plot all files which are generated by default by *Timeline_Plotter* unless a second input is given.

        Arguments:
            FigureDirectory (str): Path to the directory where the binary files are located.
            FilesToPlot (list of str): Optional. List of strings containing the names of the binary files (excluding "fig.pickle") to be plotted.

        """

        from mats_planningtool.PlotTimelinePlotterPlots.Core import (
            Plot_Timeline_Plotter_Plots, )

        Plot_Timeline_Plotter_Plots(FigureDirectory, FilesToPlot)

    def MinimalScienceXML_gen(self):
        """Invokes the *MinimalScienceXML_gen* part of the *OPT*.

        Creates an .xml file with fixed CMDs which purpose is to define a flight procedure which is ran on the satellite
        following unscheduled power termination of the payload.
        Runs startup CMDs and sets the payload in operation mode with the CCD macro *HighResIR*.
        The CMD staggering is fixed. No date is given in the generated XML and will need to be added manually.
        Uses settings for the CMDs from the currently set *Configuration File*.

        """

        from mats_planningtool.XMLGenerator.MinimalScienceXML_gen import (
            MinimalScienceXMLGenerator, )

        MinimalScienceXMLGenerator(self)

    def Timeline_analyzer(self, science_mode_timeline_path, date):
        """Invokes the Timeline_analyser program part of Operational Planning Tool.

        Searches a Science Mode Timeline json file for a given date and returns the scheduled mode and its parameters.

        Arguments:
            science_mode_timeline_path (str): path to the .json file containing the Science Mode Timeline
            date (str): A given date and time ('2019/09/05 12:09:25')

        Returns:
            (tuple): tuple containing:

                **Mode** (*str*): The currently scheduled Mode ath the given date. \n
                **Parameters** (*dict*): The parameters of the Mode. \n
        """
        from mats_planningtool.TimelineAnalyzer.Core import Timeline_analyzer

        Mode, Parameters = Timeline_analyzer(science_mode_timeline_path, date)

        return Mode, Parameters

    def Timeline_Plotter(
        self,
        Science_Mode_Path,
        OHB_H5_Path="",
        STK_CSV_PATH="",
        Timestep=16,
        FractionOfDataUsed=0.1,
    ):
        """Invokes the *Timeline_Plotter* program part of *Operational_Planning_Tool*.

        Simulates the position and attitude of MATS from a given Science Mode Timeline and also optionally compares it to
        positional and attitude data given in a .h5 data set, located at *OHB_H5_Path*. Plots both the simulated data and given data.
        The attitude data shows only the target pointing orientation and does not mimic MATS's actual attitude control system. This leads to large pointing differences whenever the pointing altitude is changed. \n
        The timesteps of both the .h5 data and the Science Mode is synchronized to allow direct comparison if possible. \n

        A .csv file, generated in STK, may also be included to plot the predicted positional error of the satellite compared to STK data. Only data points with equal timestamps to the simulated Science Mode Timeline data will be plotted.
        Saves generated plots as binary files. \n

        Settings for the operation of the program are stated in the chosen *Configuration File*.
        Settings stated in the *Science Mode Timeline* override settings given in the chosen *Configuration file*.

        Arguments:
            Science_Mode_Path (str): Path to the Science Mode Timeline to be plotted.
            OHB_H5_Path (str): *Optional*. Path to the .h5 file containing position, time, and attitude data. The .h5 file is defined in the "Ground Segment ICD" document. The timestamps for the attitude and state data is assumed to be synchronized.
            STK_CSV_PATH (str): *Optional*. Path to the .csv file containing position (column 1-3), velocity (column 4-6), and time (column 7), generated in STK. Position and velocity data is assumed to be in km and in ICRF.
            Timestep (int): *Optional*. The chosen timestep of the Science Mode Timeline simulation [s]. Drastically changes runtime of the program.

        Returns:
            (tuple): tuple containing:

                - **Data_MATS** (*dict*): Dictionary containing lists of simulated data of MATS. \n
                - **Data_LP** (*dict*): Dictionary containing lists of simulated data of LP. \n
                - **Time** (*list*): List containing timestamps (utc) of the simulated data in Data_MATS and Data_LP. \n
                - **Time_OHB** (*list*): List containing timestamps (utc) of the plotted data in the .h5 file. \n


        """
        from mats_planningtool.TimelinePlotter.Core import Timeline_Plotter

        Data_MATS, Data_LP, Time, Time_OHB = Timeline_Plotter(
            Science_Mode_Path=Science_Mode_Path,
            configFile=self,
            OHB_H5_Path=OHB_H5_Path,
            STK_CSV_FILE=STK_CSV_PATH,
            Timestep=Timestep,
            FractionOfDataUsed=FractionOfDataUsed,
        )

        return Data_MATS, Data_LP, Time, Time_OHB

    def PLUTOGenerator(
        self,
        XML_Path=None,
        PLUTO_Path="Output/pluto_script.plp",
        wait_platform=False,
        max_wait_time=None,
    ):
        """Invokes PLUTO generator

        """

        from mats_planningtool.PLUTOGenerator import PLUTOGenerator

        if XML_Path is None:
            SCIMOD_Path = (
                "Output_" + "Science_Mode_Timeline_" +
                os.path.splitext(os.path.split(self.config_file_name)[1])[0])
            print(SCIMOD_Path)
            XML_Path = os.path.join(
                "Output", "XML_TIMELINE__" + "FROM__" + SCIMOD_Path + ".xml")

        PLUTOGenerator.PLUTO_generator(self, XML_Path, PLUTO_Path,
                                       wait_platform, max_wait_time)

        return
def Snapshot_Inertial_macro(
    root,
    relativeTime,
    CCD_settings,
    FreezeTime,
    FreezeDuration,
    pointing_altitude,
    StandardPointingAltitude,
    SnapshotSpacing,
    Snapshot_relativeTime,
    Timeline_settings,
    configFile,
    comment,
):
    """ Macro that corresponds to pointing towards an Inertial direction and take a Snapshot with all the CCDs (except Nadir) which do not have TEXPMS set to 0.

    The order of the snapshots taken is determined by the CCDs TEXPMS in increasing order. This to prevent simultaneous readout.

    1. Set Payload to idle mode
    2. Point the satellite to *pointing_altitude*.
    3. Run CCD Commands with given settings.
    4. Run ArgFreezeStart Command with *FreezeTime*.
    5. Run ArgFreezeDuration Command with *FreezeDuration*.
    6. Take a Snapshot with each CCD (except Nadir and CCDs with TEXPMS=0) starting at *Snapshot_relativeTime* with a spacing of *SnapshotSpacing*.
    7. Point the satellite to *StandardPointingAltitude*.

    Arguments:
        root (lxml.etree._Element):  XML tree structure. Main container object for the ElementTree API.
        relativeTime (float): The relative starting time of the macro with regard to the start of the timeline [s]
        CCD_settings (dict of dict of int): Settings for the CCDs.. Defined in the *Configuration File*.
        FreezeTime (float): Start time of attitude freeze command in on-board time [s].
        FreezeDuration (int): Duration of freeze [s].
        pointing_altitude (int): The altitude of the tangential point [m].
        StandardPointingAltitude (int): The standard altitude of the LP  [m].
        SnapshotSpacing (int): The time in seconds inbetween snapshots of individual CCDs.
        Snapshot_relativeTime (float): The relativeTime (time from start of timeline) at which the first Snapshot is taken.
        Timeline_settings (dict): Dictionary containing the settings of the Timeline given in either the *Science_Mode_Timeline* or the *Configuration File*.
        comment (str): A comment for the macro. Will be printed in the genereated XML-file.

    Returns:
        relativeTime (float): Time in seconds equal to the input "relativeTime" with added delay from the scheduling of commands.
    """

    comment = comment + ", Snapshot_Inertial_macro"

    "TEXPIMS is unused but still need to be set to a plausible value to not get errors"
    Disregarded, Disregarded, Disregarded, TEXPIMS = Library.SyncArgCalculator(
        CCD_settings,
        Timeline_settings["CCDSYNC_ExtraOffset"],
        Timeline_settings["CCDSYNC_ExtraIntervalTime"],
    )

    "CCDSEL arguments in order of increasing TEXPMS"
    CCDSELs = Library.OrderingOfCCDSnapshots(CCD_settings)

    relativeTime = Commands.TC_pafMode(root,
                                       relativeTime,
                                       MODE=2,
                                       Timeline_settings=Timeline_settings,
                                       configFile=configFile,
                                       comment=comment)

    relativeTime = Commands.TC_acfLimbPointingAltitudeOffset(
        root,
        relativeTime,
        Initial=pointing_altitude,
        Final=pointing_altitude,
        Rate=0,
        Timeline_settings=Timeline_settings,
        configFile=configFile,
        comment=comment,
    )

    relativeTime = SetCCDs_macro(
        root,
        relativeTime,
        CCD_settings,
        TEXPIMS,
        Timeline_settings=Timeline_settings,
        configFile=configFile,
        comment=comment,
    )

    relativeTime = Commands.TC_affArgFreezeStart(
        root,
        relativeTime,
        StartTime=FreezeTime,
        Timeline_settings=Timeline_settings,
        configFile=configFile,
        comment=comment,
    )

    relativeTime = Commands.TC_affArgFreezeDuration(
        root,
        relativeTime,
        FreezeDuration=FreezeDuration,
        Timeline_settings=Timeline_settings,
        configFile=configFile,
        comment=comment,
    )

    for CCDSEL in CCDSELs:
        relativeTime = Commands.TC_pafCCDSnapshot(
            root,
            Snapshot_relativeTime,
            CCDSEL=CCDSEL,
            Timeline_settings=Timeline_settings,
            configFile=configFile,
            comment=comment,
        )
        Snapshot_relativeTime += SnapshotSpacing

    relativeTime = Commands.TC_acfLimbPointingAltitudeOffset(
        root,
        relativeTime,
        Initial=StandardPointingAltitude,
        Final=StandardPointingAltitude,
        Rate=0,
        Timeline_settings=Timeline_settings,
        configFile=configFile,
        comment=comment,
    )
    # relativeTime = Commands.TC_pafMode(root, relativeTime, MODE = 1, Timeline_settings = Timeline_settings, configFile=configFile, comment = comment)

    return relativeTime
def Snapshot_Limb_Pointing_macro(
    root,
    relativeTime,
    CCD_settings,
    pointing_altitude,
    SnapshotSpacing,
    Timeline_settings,
    configFile,
    comment,
):
    """ Macro that corresponds to pointing towards a Limb altitude and taking a Snapshot with each CCD (except Nadir).

    The order of the snapshots taken is determined by the CCDs TEXPMS in increasing order. This to prevent simultaneous readout.

    1. Set Payload to idle mode
    2. Point the satellite to *pointing_altitude*.
    3. Run CCD Commands with given settings.
    4. Take a Snapshot with each CCD (except Nadir and CCDs with TEXPMS=0) with a spacing of *SnapshotSpacing*.

    Arguments:
        root (lxml.etree._Element):  XML tree structure. Main container object for the ElementTree API.
        relativeTime (float): The relative starting time of the macro with regard to the start of the timeline [s]
        CCD_settings (dict of dict of int): Settings for the CCDs.. Defined in the *Configuration File*.
        pointing_altitude (int): The altitude of the tangential point [m].
        SnapshotSpacing (int): The time in seconds inbetween snapshots of individual CCDs.
        Timeline_settings (dict): Dictionary containing the settings of the Timeline given in either the *Science_Mode_Timeline* or the *Configuration File*.
        comment (str): A comment for the macro. Will be printed in the genereated XML-file.

    Returns:
        relativeTime (float): Time in seconds equal to the input "relativeTime" with added delay from the scheduling of commands.
    """

    comment = comment + ", Snapshot_Limb_Pointing_macro"

    Disregarded, Disregarded, Disregarded, TEXPIMS = Library.SyncArgCalculator(
        CCD_settings,
        Timeline_settings["CCDSYNC_ExtraOffset"],
        Timeline_settings["CCDSYNC_ExtraIntervalTime"],
    )
    CCDSELs = Library.OrderingOfCCDSnapshots(CCD_settings)

    relativeTime = Commands.TC_pafMode(root,
                                       relativeTime,
                                       MODE=2,
                                       Timeline_settings=Timeline_settings,
                                       configFile=configFile,
                                       comment=comment)

    # relativeTime_SnapShot = relativeTime+Timeline_settings['pointing_stabilization']
    relativeTime = Commands.TC_acfLimbPointingAltitudeOffset(
        root,
        relativeTime,
        Initial=pointing_altitude,
        Final=pointing_altitude,
        Rate=0,
        Timeline_settings=Timeline_settings,
        configFile=configFile,
        comment=comment,
    )

    relativeTime = SetCCDs_macro(
        root,
        relativeTime,
        CCD_settings,
        TEXPIMS=TEXPIMS,
        Timeline_settings=Timeline_settings,
        configFile=configFile,
        comment=comment,
    )

    for CCDSEL in CCDSELs:
        Commands.TC_pafCCDSnapshot(
            root,
            relativeTime,
            CCDSEL=CCDSEL,
            Timeline_settings=Timeline_settings,
            configFile=configFile,
            comment=comment,
        )
        relativeTime += SnapshotSpacing

    # relativeTime = Commands.TC_pafMode(root, relativeTime, MODE = 1, Timeline_settings = Timeline_settings, configFile=configFile, comment = comment)

    return relativeTime
def Limb_functional_test(
        root,
        date,
        duration,
        relativeTime,
        Timeline_settings,
        configFile,
        Test_settings={'ExpTimes': [1000, 2000, 4000, 8000, 16000]}):
    """Limb_functional_test. 

    Schedules Limb_functional_test with defined parameters and simulates MATS propagation from TLE.
    Scheduling of all daylight and nighttime commands are separated timewise and all commands for one of the two is scheduled first.
    """

    Logger.info('')
    Logger.info('Start of Limb_functional_test')

    Logger.debug('Test_settings from Science Mode List: ' + str(Test_settings))

    log_timestep = 500
    Logger.debug('log_timestep [s]: ' + str(log_timestep))

    TLE = configFile.getTLE()

    CCD_settings = configFile.CCD_macro_settings('FullReadout')

    duration_flag = 0

    JPEGQs = [100, 95]
    WDWs = [7, 128]
    altitudes = [50000, 70000, 90000, 110000, 130000, 160000, 200000]
    ExpTimes = Test_settings['ExpTimes']
    SnapshotSpacing = 5

    R_mean = 6371  # km
    Altitude_defining_night = 45  # km
    Altitude_defining_day = 25  # km

    #lat = Test_settings['lat']/180*pi
    lat = 30

    Mode_name = sys._getframe(0).f_code.co_name

    "Estimation of the angle [degrees] between the sun and the LP when it enters eclipse"
    #NightDayAngle = arccos(R_mean/(R_mean+Altitude_defining_night))/pi*180 + 90

    NightAngle = arccos(R_mean /
                        (R_mean + Altitude_defining_night)) / pi * 180 + 90
    DayAngle = arccos(R_mean /
                      (R_mean + Altitude_defining_day)) / pi * 180 + 90

    Logger.debug('')
    Logger.debug('NightAngle : ' + str(NightAngle))
    Logger.debug('DayAngle : ' + str(DayAngle))

    timestep = 4
    t = 0

    initial_relativeTime = relativeTime

    "Consecutively schedule all Macros for day, and then night"
    for mode in ['Day', 'Night']:

        "Altitudes that defines the LP"
        for altitude in altitudes:

            "Start looping the CCD settings and call for macros"
            for JPEGQ, WDW in zip(JPEGQs, WDWs):

                for key in CCD_settings.keys():
                    CCD_settings[key]['JPEGQ'] = JPEGQ
                    CCD_settings[key]['WDW'] = WDW

                for ExpTime in ExpTimes:

                    for key in CCD_settings.keys():
                        CCD_settings[key]['TEXPMS'] = ExpTime

                    ############################################################################
                    ########################## Orbit simulator #################################
                    ############################################################################

                    MATS_skyfield = skyfield.api.EarthSatellite(TLE[0], TLE[1])

                    "Calculate the current angle between MATS and the Sun and the latitude of the LP"
                    "and Loop until it is either day or night and the right latitude"
                    while (True):

                        mode_relativeTime = relativeTime - initial_relativeTime
                        current_time = ephem.Date(date + ephem.second *
                                                  mode_relativeTime)
                        current_time_datetime = current_time.datetime()

                        if (mode_relativeTime > duration
                                and duration_flag == 0):
                            Logger.warning(
                                'Warning!! The scheduled time for the Test has ran out.'
                            )
                            #input('Enter anything to continue:\n')
                            duration_flag = 1

                        if (t * timestep % log_timestep == 0):
                            LogFlag = True
                        else:
                            LogFlag = False

                        Satellite_dict = Library.Satellite_Simulator(
                            MATS_skyfield, current_time, Timeline_settings,
                            altitude / 1000, LogFlag, Logger)

                        r_MATS = Satellite_dict['Position [km]']
                        lat_MATS = Satellite_dict['Latitude [degrees]']
                        lat_LP = Satellite_dict[
                            'EstimatedLatitude_LP [degrees]']

                        sun_angle = Library.SunAngle(r_MATS, current_time)

                        if (LogFlag == True):
                            Logger.debug('sun_angle: ' + str(sun_angle))

                        if ((sun_angle < DayAngle and abs(lat_LP) <= lat
                             and mode == 'Day') or
                            (sun_angle > NightAngle and abs(lat_LP) <= lat
                             and mode == 'Night')):

                            Logger.debug('!!Break of Loop!!')
                            Logger.debug('Loop Counter (t): ' + str(t))
                            Logger.debug('current_time: ' + str(current_time))
                            Logger.debug('lat_MATS [degrees]: ' +
                                         str(lat_MATS))
                            Logger.debug('lat_LP [degrees]: ' + str(lat_LP))
                            Logger.debug('sun_angle [degrees]: ' +
                                         str(sun_angle))
                            Logger.debug('mode: ' + str(mode))
                            Logger.debug('')

                            break

                        "Increase Loop counter"
                        t = t + 1

                        "Timestep for propagation of MATS"
                        relativeTime = round(relativeTime + timestep, 2)

                    ############################################################################
                    ########################## End of Orbit simulator ##########################
                    ############################################################################

                    comment = (Mode_name + ', ' + str(date) + ', ' +
                               str(mode) + ', pointing_altitude = ' +
                               str(altitude) + ', ExpTime = ' + str(ExpTime) +
                               ', JPEGQ = ' + str(JPEGQ))
                    Logger.debug(comment)

                    relativeTime = Macros.Snapshot_Limb_Pointing_macro(
                        root,
                        round(relativeTime, 2),
                        CCD_settings,
                        pointing_altitude=altitude,
                        SnapshotSpacing=SnapshotSpacing,
                        Timeline_settings=Timeline_settings,
                        configFile=configFile,
                        comment=comment)

                    # relativeTime = Macros.Limb_functional_test_macro(root = root, relativeTime = str(relativeTime),
                    #                           pointing_altitude = str(altitude), ExpTime = str(ExpTime),
                    #                           JPEGQ = JPEGQ, comment=comment)

                    #"Postpone next command until at least the end of ExpTime"
                    #relativeTime = round(relativeTime + ExpTime/1000,2)

    mode_relativeTime = relativeTime - initial_relativeTime
    current_time = ephem.Date(date + ephem.second * mode_relativeTime)

    Logger.info('End of Limb_functional_test')

    return relativeTime, current_time
def Operational_Limb_Pointing_macro(
    root,
    relativeTime,
    CCD_settings,
    PM_settings,
    pointing_altitude,
    Timeline_settings,
    configFile,
    comment="",
):
    """ Macro that corresponds to pointing towards a Limb altitude in Operational Mode.

    1. Set Payload to idle mode
    2. Point the satellite to *pointing_altitude*.
    3. Run PM Command with given settings.
    4. Run CCD Synchronize Command with calculated settings.
    5. Run CCD Commands with given settings.
    6. Set Payload to operational mode

    Arguments:
        root (lxml.etree._Element):  XML tree structure. Main container object for the ElementTree API.
        relativeTime (float): The relative starting time of the macro with regard to the start of the timeline [s]
        CCD_settings (:obj:`dict` of :obj:`dict` of int): Settings for the CCDs. Defined in the *Configuration File*.
        pointing_altitude (int): The altitude of the tangential point [m].
        Timeline_settings (dict): Dictionary containing the settings of the Timeline given in either the *Science_Mode_Timeline* or the *Configuration File*.
        comment (str): A comment for the macro. Will be printed in the genereated XML-file.

    Returns:
        relativeTime (float): Time in seconds equal to the input "relativeTime" with added delay from the scheduling of commands.
    """

    comment = comment + ", Operational_Limb_Pointing_macro"

    CCDSEL, NCCD, TEXPIOFS, TEXPIMS = Library.SyncArgCalculator(
        CCD_settings,
        Timeline_settings["CCDSYNC_ExtraOffset"],
        Timeline_settings["CCDSYNC_ExtraIntervalTime"],
    )

    relativeTime = Commands.TC_pafMode(root,
                                       relativeTime,
                                       MODE=2,
                                       Timeline_settings=Timeline_settings,
                                       configFile=configFile,
                                       comment=comment)

    # relativeTime_OperationalMode = relativeTime+Timeline_settings['pointing_stabilization']
    relativeTime = Commands.TC_acfLimbPointingAltitudeOffset(
        root,
        relativeTime,
        Initial=pointing_altitude,
        Final=pointing_altitude,
        Rate=0,
        Timeline_settings=Timeline_settings,
        configFile=configFile,
        comment=comment,
    )

    relativeTime = Commands.TC_pafPM(
        root,
        relativeTime,
        TEXPMS=PM_settings["TEXPMS"],
        TEXPIMS=PM_settings["TEXPIMS"],
        Timeline_settings=Timeline_settings,
        configFile=configFile,
        comment=comment,
    )

    relativeTime = SetCCDs_macro(
        root,
        relativeTime,
        CCD_settings=CCD_settings,
        TEXPIMS=TEXPIMS,
        Timeline_settings=Timeline_settings,
        configFile=configFile,
        comment=comment,
    )

    relativeTime = Commands.TC_pafCCDSYNCHRONIZE(
        root,
        relativeTime,
        CCDSEL=CCDSEL,
        NCCD=NCCD,
        TEXPIOFS=TEXPIOFS,
        Timeline_settings=Timeline_settings,
        configFile=configFile,
        comment=comment,
    )

    relativeTime = Commands.TC_pafMode(root,
                                       relativeTime,
                                       MODE=1,
                                       Timeline_settings=Timeline_settings,
                                       configFile=configFile,
                                       comment=comment)

    return relativeTime
def Nadir_functional_test(
        root,
        date,
        duration,
        relativeTime,
        Timeline_settings,
        configFile,
        Test_settings={'ExpTimes': [1000, 2000, 4000, 8000, 16000]}):
    """Nadir_functional_test

    Schedules Nadir_functional_test with defined parameters and simulates MATS propagation from TLE.
    Scheduling of all daylight and nighttime commands are separated timewise and all commands for one of the two is scheduled first.
    """

    Logger.info('')
    Logger.info('Start of Nadir_functional_test')

    Logger.debug('Test_settings from Science Mode List: ' + str(Test_settings))

    CCD_settings = configFile.CCD_macro_settings('FullReadout')
    altitude = Timeline_settings['StandardPointingAltitude']

    ExpTimes = Test_settings['ExpTimes']

    log_timestep = 500
    Logger.debug('log_timestep [s]: ' + str(log_timestep))

    duration_flag = 0

    JPEGQs = [100, 95]
    WDWs = [7, 128]

    initial_relativeTime = relativeTime

    TLE = configFile.getTLE()
    MATS_skyfield = skyfield.api.EarthSatellite(TLE[0], TLE[1])

    Altitude_defining_night = 45  # km
    Altitude_defining_day = 25  # km
    R_mean = 6371

    # Estimation of the angle [degrees] between the sun and the FOV position when it enters eclipse
    NightAngle = arccos(R_mean /
                        (R_mean + Altitude_defining_night)) / pi * 180 + 90
    DayAngle = arccos(R_mean /
                      (R_mean + Altitude_defining_day)) / pi * 180 + 90

    Logger.debug('')
    Logger.debug('DayAngle : ' + str(DayAngle))
    Logger.debug('NightAngle : ' + str(NightAngle))
    Logger.debug('')

    withinLat = 30

    Mode_name = sys._getframe(0).f_code.co_name

    t = 0
    timestep = 4

    # latMargin = 360/5400 * timestep * 5 #Overly estimated change of latitude per timestep

    "Start looping the CCD nadir settings and call for macros"
    for mode in ['Day', 'Night']:

        for JPEGQ, WDW in zip(JPEGQs, WDWs):

            CCD_settings[64]['JPEGQ'] = JPEGQ
            CCD_settings[64]['WDW'] = WDW

            for ExpTime in ExpTimes:

                CCD_settings[64]['TEXPMS'] = ExpTime

                ############################################################################
                ########################## Orbit simulator #################################
                ############################################################################

                # for lat in [-30, 0, 30]:

                "Calculate the current angle between MATS and the Sun"
                "and Loop until it is either day or night and the right latitude"
                while (True):

                    mode_relativeTime = relativeTime - initial_relativeTime
                    current_time = ephem.Date(date +
                                              ephem.second * mode_relativeTime)

                    if (mode_relativeTime > duration and duration_flag == 0):
                        Logger.warning(
                            'Warning!! The scheduled time for the Test has ran out.'
                        )
                        #input('Enter anything to continue:\n')
                        duration_flag = 1

                    if (t * timestep % log_timestep == 0):
                        LogFlag = True
                    else:
                        LogFlag = False

                    Satellite_dict = Library.Satellite_Simulator(
                        MATS_skyfield, current_time, Timeline_settings,
                        altitude / 1000, LogFlag, Logger)

                    r_MATS = Satellite_dict['Position [km]']
                    lat_MATS = Satellite_dict['Latitude [degrees]']

                    sun_angle = Library.SunAngle(r_MATS, current_time)

                    if (t * timestep % log_timestep == 0 == 0 or t == 1):
                        Logger.debug('')
                        Logger.debug('current_time: ' + str(current_time))
                        Logger.debug('lat_MATS [degrees]: ' + str(lat_MATS))
                        Logger.debug('sun_angle [degrees]: ' + str(sun_angle))
                        Logger.debug('mode: ' + str(mode))
                        Logger.debug('')

                    # if( (sun_angle < DayAngle and lat-latMargin <= lat_MATS <= lat+latMargin and mode == 'Day' ) or
                    #   (sun_angle > NightAngle and lat-latMargin <= lat_MATS <= lat+latMargin and mode == 'Night' )):
                    if ((sun_angle < DayAngle and abs(lat_MATS) < withinLat
                         and mode == 'Day') or
                        (sun_angle > NightAngle and abs(lat_MATS) < withinLat
                         and mode == 'Night')):

                        Logger.debug('!!Break of Loop!!')
                        Logger.debug('Loop Counter (t): ' + str(t))
                        Logger.debug('current_time: ' + str(current_time))
                        Logger.debug('lat_MATS [degrees]: ' + str(lat_MATS))
                        Logger.debug('sun_angle [degrees]: ' + str(sun_angle))
                        Logger.debug('mode: ' + str(mode))

                        Logger.debug('')
                        break

                    "Increase Loop counter"
                    t = t + 1

                    "Timestep for propagation of MATS"
                    relativeTime = round(relativeTime + timestep, 1)

                ############################################################################
                ########################## End of Orbit simulator ##########################
                ############################################################################

                comment = (Mode_name + ', ' + str(date) + ', ' +
                           ', pointing_altitude = ' + str(altitude) +
                           ', ExpTime = ' + str(ExpTime) + ', JPEGQ = ' +
                           str(JPEGQ))

                Logger.debug(comment)

                relativeTime = Macros.NadirSnapshot_Limb_Pointing_macro(
                    root=root,
                    relativeTime=relativeTime,
                    pointing_altitude=altitude,
                    CCD_settings=CCD_settings,
                    Timeline_settings=Timeline_settings,
                    configFile=configFile,
                    comment=comment)

                #"Postpone next command until at least the end of ExpTime"
                #relativeTime = round(float(relativeTime) + ExpTime/1000,2)

    mode_relativeTime = relativeTime - initial_relativeTime
    current_time = ephem.Date(date + ephem.second * mode_relativeTime)

    Logger.info('End of Nadir_functional_test')

    return relativeTime, current_time
Exemple #10
0
def Timeline_generator(configFile):
    """The core function of the *Timeline_gen* program, part of Operational Planning Tool.

    Returns:
        None

    """

    "######## Try to Create a directory for storage of output files #######"
    try:
        os.mkdir('Output')
    except:
        pass

    "############# Set up Logger #################################"
    Library.SetupLogger(configFile.Logger_name())
    "#############################################################"

    Logger.info('Start of program')

    Version = configFile.Version()
    Logger.info('Configuration File used: ' + configFile.config_file_name +
                ', Version: ' + Version)

    "Get settings for the timeline"
    Timeline_settings = configFile.Timeline_settings()

    Timeline_start_date = ephem.Date(Timeline_settings['start_date'])
    Logger.debug('Timeline_settings: ' + str(Timeline_settings))

    "Check if yaw_correction setting is set correct"
    if (Timeline_settings['yaw_correction'] == True):
        Logger.info('Yaw correction is on')
    elif (Timeline_settings['yaw_correction'] == False):
        Logger.info('Yaw correction is off')
    else:
        Logger.error(
            'configFile.Timeline_settings["yaw_correction"] is set wrong')
        raise TypeError

    "Get a List of Modes and CMDs in a prioritized order which are to be scheduled"
    Scheduling_priority = configFile.Scheduling_priority()
    Logger.info('Scheduling priority list: ' + str(Scheduling_priority))

    SCIMOD_Timeline_unchronological = []

    "Create Occupied_Timeline dictionary with keys equal to keys of Scheduling_priority"
    Occupied_Timeline = {key: [] for key in Scheduling_priority}
    "Create scheduled_instances dictionary with keys equal to keys of Scheduling_priority. This will keep track of how many times something is scheduled"
    scheduled_instances = {key: 0 for key in Scheduling_priority}

    "Reset"
    configFile.Mode120Iteration = 1
    configFile.Mode124Iteration = 1

    Logger.debug('')
    Logger.debug('Occupied_Timeline: \n' + "{" +
                 "\n".join("        {}: {}".format(k, v)
                           for k, v in Occupied_Timeline.items()) + "}")
    Logger.debug('')

    Logger.info('')
    Logger.info('Start looping through modes priority list')

    "################################################################################################################"
    "################################################################################################################"

    "Loop through the Modes to be ran and schedule each one in the priority order of which they appear in the list"
    for x in range(len(Scheduling_priority)):

        Logger.info('')
        Logger.info('Iteration ' + str(x + 1) + ' in Mode scheduling loop')

        "The name of the Science Mode to be scheduled"
        scimod = Scheduling_priority[x]

        Logger.info('Start of ' + scimod)
        Logger.info('')

        "Get the function of the same name as the string in Scheduling_priority"
        try:
            Mode_function = getattr(Modes_Header, scimod)
        except:
            Logger.error(
                scimod +
                ' in Scheduling_priority was not found in Modes.Modes_Header')
            raise NameError

        "Call the function of the same name as the string in Scheduling_priority"
        Occupied_Timeline, Mode_comment = Mode_function(
            Occupied_Timeline, configFile)

        Logger.debug('')
        Logger.debug('Post-' + scimod + ' Occupied_Timeline: \n' + "{" +
                     "\n".join("        {}: {}".format(k, v)
                               for k, v in Occupied_Timeline.items()) + "}")
        Logger.debug('')

        "Check if a new date was scheduled"
        if (len(Occupied_Timeline[scimod]) != scheduled_instances[scimod]):

            #scheduled_instances[scimod] = len(Occupied_Timeline[scimod])
            "Save the number of times something has been scheduled to allow multiple instances of it to be saved"
            scheduled_instances[scimod] += 1

            "To allow multiple instances of Mode120/124 to be scheduled using the V_offset settings"
            if (scimod == 'Mode120'):
                configFile.Mode120Iteration += 1
            elif (scimod == 'Mode124'):
                configFile.Mode124Iteration += 1

            "Check if the scheduled date is within the time defined for the timeline"
            if (Occupied_Timeline[scimod][scheduled_instances[scimod] - 1][0] <
                    Timeline_start_date
                    or Occupied_Timeline[scimod][scheduled_instances[scimod] -
                                                 1][0] >
                (Timeline_start_date +
                 ephem.second * Timeline_settings['duration']['duration'])
                    or Occupied_Timeline[scimod][scheduled_instances[scimod] -
                                                 1][1] -
                    Occupied_Timeline[scimod][scheduled_instances[scimod] -
                                              1][0] >
                    Timeline_settings['duration']['duration']):
                Logger.error(
                    scimod +
                    ' scheduled outside of timeline as defined in configFile')

                #input('Enter anything to acknowledge and continue\n')

            "Append mode and dates and comment to an unchronological Science Mode Timeline"
            SCIMOD_Timeline_unchronological.append(
                (Occupied_Timeline[scimod][scheduled_instances[scimod] - 1][0],
                 Occupied_Timeline[scimod][scheduled_instances[scimod] - 1][1],
                 scimod, Mode_comment))
            Logger.debug('Entry number ' +
                         str(len(SCIMOD_Timeline_unchronological)) +
                         ' in unchronological Science Mode list: ' +
                         str(SCIMOD_Timeline_unchronological[-1]))
            Logger.debug('')

    "###########################################################################################################"
    "###########################################################################################################"

    Logger.info('Looping sequence of modes priority list complete')
    Logger.info('')

    "############################################################################################################"
    "########## Scheduling of operational science mode (Either Mode1, 2 or 5) ###################################"

    Logger.info('Operational Science Mode scheduling')

    Mode1_2_5 = getattr(Modes_Header, 'Mode1_2_5')

    if (Timeline_settings['Choose_Operational_Science_Mode'] == 5):
        Logger.info('Schedule Mode5 as an operational science mode')

        OpSciMode = 'Mode5'
    elif (Timeline_settings['Choose_Operational_Science_Mode'] == 1):
        Logger.info('Schedule Mode1 as an operational science mode')
        OpSciMode = 'Mode1'
    elif (Timeline_settings['Choose_Operational_Science_Mode'] == 2):
        Logger.info('Schedule Mode2 as an operational science mode')
        OpSciMode = 'Mode2'
    elif (Timeline_settings['Choose_Operational_Science_Mode'] == 0):
        ### Check if it is NLC season ###
        if (Timeline_start_date.tuple()[1] in [11, 12, 1, 2, 5, 6, 7, 8]
                or (Timeline_start_date.tuple()[1] in [3, 9]
                    and Timeline_start_date.tuple()[2] in range(11))):

            Logger.info('NLC season (Mode1)')
            OpSciMode = 'Mode1'
        else:
            Logger.info('Not NLC season (Mode2)')
            OpSciMode = 'Mode2'

    Occupied_Timeline.update({OpSciMode: []})

    Occupied_Timeline, Mode_comment = Mode1_2_5(Occupied_Timeline, configFile)
    Logger.debug('')
    Logger.debug('Post-' + OpSciMode + ' Occupied_Timeline: \n' + "{" +
                 "\n".join("        {}: {}".format(k, v)
                           for k, v in Occupied_Timeline.items()) + "}")
    Logger.debug('')

    Logger.debug(OpSciMode + ' getting added to unchronological timeline')
    for x in range(len(Occupied_Timeline[OpSciMode])):
        Logger.debug('Appended to timeline: ' +
                     str((Occupied_Timeline[OpSciMode][x][0],
                          Occupied_Timeline[OpSciMode][x][1], OpSciMode,
                          Mode_comment)))
        SCIMOD_Timeline_unchronological.append(
            (Occupied_Timeline[OpSciMode][x][0],
             Occupied_Timeline[OpSciMode][x][1], OpSciMode, Mode_comment))

    "###########################################################################################"
    "###########################################################################################"

    "#############################################################################################"
    "################# Sort Planned Modes and create a Science Mode Timeline List ################"

    SCIMOD_Timeline_unchronological.sort()

    "Create a Science Mode Timeline list with the first entry being Timeline_settings"
    SCIMOD_Timeline = []
    SCIMOD_Timeline.append([
        'Timeline_settings',
        'This Timeline was created using these settings from ' +
        configFile.config_file_name,
        'Note: These Timeline_settings and TLE will be used when converting into a XML',
        Timeline_settings,
        configFile.getTLE()
    ])

    Logger.debug('1 entry in Science Mode list: ' + str(SCIMOD_Timeline[0]))

    t = 0
    "Add entries to the Science Mode Timeline list in chronological order. The entries in the list contains Mode name, start date, endDate, settings and comment"
    for x in SCIMOD_Timeline_unchronological:

        Logger.debug(str(t + 1) + ' Timeline entry: ' + str(x))

        Logger.debug(
            'Get the parameters for XML-gen from mats_planningtool_Config_File and add them to Science Mode timeline'
        )
        try:
            Config_File = getattr(configFile, x[2] + '_settings')()
        except AttributeError:

            if (x[2] == 'Mode1' or x[2] == 'Mode2' or x[2] == 'Mode5'):

                Config_File = getattr(configFile,
                                      'Operational_Science_Mode_settings')()

            elif (x[2] == 'ArgEnableYawComp'):
                Config_File = {
                    'EnableYawComp': int(Timeline_settings['yaw_correction'])
                }

            elif (x[2] == 'HTR'):
                Config_File = {
                    'HTRSEL': '?',
                    'SET': '?',
                    'PVALUE': '?',
                    'IVALUE': '?',
                    'DVALUE': '?'
                }

            elif (x[2] == 'Payload_Power_Toggle' or x[2] == 'TurnONCCDs'):
                Config_File = {}
            else:
                Logger.warning('No Config function for ' + x[2])
                Config_File = {}

        #SCIMOD_Timeline.append([ x[2],str(x[0]), str(x[1]),{},x[3] ])

        SCIMOD_Timeline.append([x[2], str(x[0]), str(x[1]), Config_File, x[3]])
        Logger.debug(
            str(t + 2) + ' entry in Science Mode list: ' +
            str(SCIMOD_Timeline[t + 1]))
        Logger.debug('')
        t = t + 1

    "###########################################################################################"

    "Write the Science Mode Timeline list to a .json file"
    try:
        os.mkdir('Output')
    except:
        pass
    SCIMOD_NAME = os.path.join(
        'Output', 'Science_Mode_Timeline_' +
        os.path.split(configFile.config_file_name)[1])
    Logger.info('Save mode timeline to file: ' + SCIMOD_NAME)
    with open(SCIMOD_NAME, "w") as write_file:
        json.dump(SCIMOD_Timeline, write_file, indent=2)

    "Reset temporary Globals"
    configFile.Mode120Iteration = 1
    configFile.Mode124Iteration = 1
    logging.shutdown()
def CheckConfigFile(configFile):
    """ Core function of *CheckConfigFile*.

    Checks the values given in the *Configuration File* set by *Set_ConfigFile* and raises an error if any settings are found to be incompatible.
    Also prints out the currently selected *Configuration File* and the starting date and TLE it currently uses.

    """

    Library.SetupLogger(configFile.Logger_name())

    Timeline_settings = configFile.Timeline_settings()
    Operational_Science_Mode_settings = (
        configFile.Operational_Science_Mode_settings())
    # Mode5_settings = configFile.Mode5_settings()
    Mode100_settings = configFile.Mode100_settings()
    Mode110_settings = configFile.Mode110_settings()
    Mode120_settings = configFile.Mode120_settings()
    Mode121_settings = configFile.Mode121_settings()
    Mode122_settings = configFile.Mode122_settings()
    Mode123_settings = configFile.Mode123_settings()
    Mode121_122_123_settings = configFile.Mode121_122_123_settings()
    Mode124_settings = configFile.Mode124_settings()
    Mode130_settings = configFile.Mode130_settings()
    Mode131_settings = configFile.Mode131_settings()
    Mode132_settings = configFile.Mode132_settings()
    Mode133_settings = configFile.Mode133_settings()
    Mode134_settings = configFile.Mode134_settings()

    try:
        Logger.info("Currently used Configuration File: " +
                    configFile.config_file_name)
    except:
        Logger.error(
            "Currently stated Configuration File is invalid. Try running Set_ConfigFile."
        )
        raise ValueError
    try:
        Logger.info("Currently used starting date: " +
                    Timeline_settings["start_date"])
    except:
        Logger.error(
            "Currently stated starting date is invalid. Try running Set_ConfigFile."
        )
        raise ValueError
    try:
        Logger.info("Currently used TLE: " + str(configFile.getTLE()))
    except:
        Logger.error(
            "Currently stated TLE is invalid. Try running Set_ConfigFile.")
        raise ValueError

    if not (Timeline_settings["duration"]["duration"] > 0
            and type(Timeline_settings["duration"]["duration"]) == int):
        Logger.error('Timeline_settings["duration"]["duration"]')
        raise ValueError
    if not (43099.5 < ephem.Date(Timeline_settings["start_date"]) < 73049.5
            and type(Timeline_settings["start_date"]) == str):
        Logger.error('Timeline_settings["start_date"]')
        raise ValueError
    if not (30 <= Timeline_settings["CMD_duration"]
            and type(Timeline_settings["CMD_duration"]) == int):
        Logger.error('Timeline_settings["CMD_duration"]')
        raise ValueError
    if not (15 <= Timeline_settings["mode_separation"]
            and type(Timeline_settings["mode_separation"]) == int):
        Logger.error('Timeline_settings["mode_separation"]')
        raise ValueError
    if not (1 <= Timeline_settings["CMD_separation"] <= 10 and
            (type(Timeline_settings["CMD_separation"]) == int
             or type(Timeline_settings["CMD_separation"]) == float)):
        Logger.error('Timeline_settings["CMD_separation"]')
        raise ValueError
    # if not( Timeline_settings['CMD_separation'] * 8 <= Timeline_settings['mode_separation'] ):
    #    Logger.error("Timeline_settings['CMD_separation'] * 8 <= Timeline_settings['mode_separation']. Possibility of time separation between CMDs are too large causing CMDs from Science Modes to overlap")
    #    raise ValueError
    if not (40 <= Timeline_settings["pointing_stabilization"]
            and type(Timeline_settings["pointing_stabilization"]) == int):
        Logger.error("Timeline_settings['pointing_stabilization']")
        raise ValueError
    if not (10000 <= Timeline_settings["StandardPointingAltitude"] <= 300000
            and type(Timeline_settings["StandardPointingAltitude"]) == int):
        Logger.error("Timeline_settings['StandardPointingAltitude']")
        raise ValueError
    if not (Timeline_settings["pointing_stabilization"] * 2 <
            Timeline_settings["Mode1_2_5_minDuration"]
            and type(Timeline_settings["Mode1_2_5_minDuration"]) == int):
        Logger.error("Timeline_settings['Mode1_2_5_minDuration']")
        raise ValueError
    if not (type(Timeline_settings["yaw_correction"]) == bool):
        Logger.error("Timeline_settings['yaw_correction']")
        raise TypeError
    if not (Timeline_settings["Choose_Operational_Science_Mode"]
            in [0, 1, 2, 5]):
        Logger.error(
            "Timeline_settings['Choose_Operational_Science_Mode'] != 0, 1, 2, or 5"
        )
        raise ValueError
    if not (0 <= abs(Timeline_settings["yaw_amplitude"]) < 20 and
            (type(Timeline_settings["yaw_amplitude"]) == int
             or type(Timeline_settings["yaw_amplitude"]) == float)):
        Logger.error("Timeline_settings['yaw_amplitude']")
        raise ValueError
    if not (0 <= abs(Timeline_settings["yaw_phase"]) and
            (type(Timeline_settings["yaw_phase"]) == int
             or type(Timeline_settings["yaw_phase"]) == float)):
        Logger.error("Timeline_settings['yaw_phase']")
        raise ValueError

    for key in Operational_Science_Mode_settings.keys():

        if key == "Choose_Mode5CCDMacro":
            if not (Operational_Science_Mode_settings[key] in [
                    "CustomBinning",
                    "HighResUV",
                    "HighResIR",
                    "LowPixel",
                    "FullReadout",
                    "BinnedCalibration",
            ]):
                Logger.error(
                    'Operational_Science_Mode_settings["Choose_Mode5CCDMacro"]'
                )
                raise ValueError

        else:
            if key == "timestep":
                if not (Operational_Science_Mode_settings[key] < 50):
                    Logger.error(
                        'Operational_Science_Mode_settings["timestep"]')
                    raise ValueError
            elif key == "lat":
                if not (0 <= Operational_Science_Mode_settings[key] <= 90):
                    Logger.error('Operational_Science_Mode_settings["lat"]')
                    raise ValueError
            elif not (Operational_Science_Mode_settings[key] > 0
                      and type(Operational_Science_Mode_settings[key]) == int):
                Logger.error("Operational_Science_Mode_settings")
                raise ValueError

    # if not( 10000 <= Mode5_settings['pointing_altitude'] <= 300000 and type(Mode5_settings['pointing_altitude']) == int ):
    #    Logger.error("Mode5_settings['pointing_altitude']")
    #    raise ValueError

    if not (32 < Mode100_settings["pointing_duration"]
            and type(Mode100_settings["pointing_duration"]) == int):
        Logger.error("Mode100_settings['pointing_duration']")
        raise ValueError
    if not (type(Mode100_settings["pointing_altitude_interval"]) == int
            and type(Mode100_settings["pointing_altitude_to"]) == int
            and type(Mode100_settings["pointing_altitude_from"]) == int):
        Logger.error("Mode100_settings")
        raise TypeError
    if not (abs(Mode100_settings["pointing_altitude_interval"]) <=
            abs(Mode100_settings["pointing_altitude_to"] -
                Mode100_settings["pointing_altitude_from"])):
        Logger.error("Mode100_settings['pointing_altitude_interval']")
        raise ValueError
    if not (0 < Mode100_settings["pointing_altitude_interval"] *
            (Mode100_settings["pointing_altitude_to"] -
             Mode100_settings["pointing_altitude_from"])):
        Logger.error("Mode100_settings")
        raise ValueError
    if not (type(Mode100_settings["start_date"]) == str):
        Logger.error("Mode100_settings['start_date']")
        raise TypeError
    if not (type(Mode100_settings["Exp_Time_IR"]) == int
            and type(Mode100_settings["Exp_Time_UV"]) == int):
        Logger.error(
            "Mode100_settings['Exp_Time_IR'] or Mode100_settings['Exp_Time_UV']"
        )
        raise TypeError
    numberOfAltitudes = int(
        abs(Mode100_settings["pointing_altitude_to"] -
            Mode100_settings["pointing_altitude_from"]) /
        abs(Mode100_settings["pointing_altitude_interval"]) + 1)
    if not (Mode100_settings["Exp_Time_IR"] +
            numberOfAltitudes * Mode100_settings["ExpTime_step"] <
            Mode100_settings["pointing_duration"] * 1000):
        Logger.error("Mode100_settings['pointing_duration']")
        raise ValueError
    if not (Mode100_settings["Exp_Time_UV"] +
            numberOfAltitudes * Mode100_settings["ExpTime_step"] <
            Mode100_settings["pointing_duration"] * 1000):
        Logger.error("Mode100_settings['pointing_duration']")
        raise ValueError

    for key in Mode110_settings.keys():

        if key == "start_date":
            if not (type(Mode110_settings[key]) == str):
                Logger.error("Mode110_settings")
                raise ValueError
        elif key == "pointing_altitude_from" or key == "pointing_altitude_to":
            if not (-60000 <= Mode110_settings[key] <= 230000):
                Logger.error("Mode110_settings")
                raise ValueError
        elif key == "sweep_rate":
            if not (-5000 <= Mode110_settings[key] <= 5000
                    and Mode110_settings[key] != 0):
                Logger.error("Mode110_settings")
                raise ValueError
        else:
            if not (Mode110_settings[key] > 0
                    and type(Mode110_settings[key]) == int):
                Logger.error("Mode110_settings")
                raise ValueError

    if sign(Mode110_settings["pointing_altitude_to"] -
            Mode110_settings["pointing_altitude_from"]) != sign(
                Mode110_settings["sweep_rate"]):
        Logger.error("Mode110_settings")
        raise ValueError

    if not (-60000 <= Mode120_settings["pointing_altitude"] <= 230000
            and type(Mode120_settings["pointing_altitude"]) == int):
        Logger.error("Mode120_settings['pointing_altitude']")
        raise ValueError
    if not (0 < Mode120_settings["timestep"] <= 10
            and type(Mode120_settings["timestep"]) == int):
        Logger.error("Mode120_settings['timestep']")
        raise ValueError
    if not (Mode120_settings["freeze_start"] >=
            Timeline_settings["pointing_stabilization"] +
            12 * Timeline_settings["CMD_separation"]
            and type(Mode120_settings["freeze_start"]) == int):
        Logger.error("Mode120_settings")
        raise TypeError
    if not (type(Mode120_settings["V_offset"]) == list):
        Logger.error("Mode120_settings['V_offset'] != list")
    for x in range(len(Mode120_settings["V_offset"])):
        if not (abs(Mode120_settings["V_offset"][x]) <= 10
                and 0 < abs(Mode120_settings["H_offset"]) <= 10):
            Logger.error(
                "Mode120_settings['V_offset'] or Mode120_settings['H_offset']")
            raise ValueError
    if not (type(Mode120_settings["start_date"]) == str):
        Logger.error("Mode120_settings['date']")
        raise TypeError
    if not (type(Mode120_settings["automatic"]) == bool):
        Logger.error("Mode120_settings['automatic']")
        raise TypeError
    if not (type(Mode120_settings["Vmag"]) == str):
        Logger.error("Mode120_settings['Vmag']")
    if not (0 < Mode120_settings["SnapshotTime"]
            and type(Mode120_settings["SnapshotTime"]) == int):
        Logger.error("Mode120_settings['SnapshotTime']")
        raise ValueError
    if not (2 <= Mode120_settings["SnapshotSpacing"]
            and type(Mode120_settings["SnapshotSpacing"]) == int):
        Logger.error("Mode120_settings['SnapshotSpacing']")
        raise ValueError
    if not (0 < Mode120_settings["TimeSkip"]["TimeSkip"] <= 2 * 3600 * 24 and
            (type(Mode120_settings["TimeSkip"]["TimeSkip"]) == int
             or type(Mode120_settings["TimeSkip"]["TimeSkip"]) == float)):
        Logger.error("Mode120_settings['TimeSkip']['TimeSkip']")
        raise ValueError
    if not (Timeline_settings["StandardPointingAltitude"] <
            Mode120_settings["pointing_altitude"]
            and type(Mode120_settings["pointing_altitude"]) == int):
        Logger.error("Mode120_settings['pointing_altitude']")
        raise ValueError
    if not (Mode120_settings["TimeToConsider"]["TimeToConsider"] <=
            Timeline_settings["duration"]["duration"]):
        Logger.error(
            "Mode120_settings['TimeToConsider']['TimeToConsider'] > Timeline_settings['duration']['duration']"
        )
        raise ValueError
    if not (type(Mode120_settings["CCDSELs"]) == list):
        Logger.error("Mode120_settings['CCDSELs'] != list")
        raise TypeError
    if not (Mode120_settings["SnapshotSpacing"] *
            (len(Mode120_settings["CCDSELs"]) - 1) +
            Mode120_settings["SnapshotTime"] <
            Mode120_settings["freeze_duration"] <= 3600):
        Logger.error(
            "Mode120_settings['SnapshotSpacing'] * (len(Mode120_settings['CCDSELs'])-1) + Mode120_settings['SnapshotTime'] > Mode120_settings['freeze_duration'] or Mode120_settings['freeze_duration'] > 3600"
        )
        raise ValueError
    for CCDSEL in Mode120_settings["CCDSELs"]:
        if not (CCDSEL in [1, 2, 4, 8, 16, 32]):
            Logger.error(
                "Mode120_settings['CCDSELs'] element != [1,2,4,8,16,32]")
            raise ValueError

    if not (-60000 <= Mode121_122_123_settings["pointing_altitude"] <= 230000
            and type(Mode121_122_123_settings["pointing_altitude"]) == int):
        Logger.error("Mode121_122_123_settings['pointing_altitude']")
        raise ValueError
    if not (0 < Mode121_122_123_settings["timestep"] <= 10
            and type(Mode121_122_123_settings["timestep"]) == int):
        Logger.error("Mode121_122_123_settings['timestep']")
        raise ValueError
    if not (Mode121_122_123_settings["freeze_start"] >=
            Timeline_settings["pointing_stabilization"] +
            12 * Timeline_settings["CMD_separation"]
            and type(Mode121_122_123_settings["freeze_start"]) == int):
        Logger.error("Mode121_122_123_settings")
        raise TypeError
    if not (0 < abs(Mode121_122_123_settings["V_FOV"]) <= 5
            and 0 < abs(Mode121_122_123_settings["H_FOV"]) <= 10):
        Logger.error(
            "Mode121_122_123_settings['V_FOV'] or Mode121_122_123_settings['H_FOV']"
        )
        raise ValueError
    if not (type(Mode121_122_123_settings["Vmag"]) == str):
        Logger.error("Mode121_122_123_settings['Vmag']")
        raise TypeError
    if not (0 < Mode121_122_123_settings["SnapshotTime"] <
            Mode121_122_123_settings["freeze_duration"] - 10
            and type(Mode121_122_123_settings["SnapshotTime"]) == int):
        Logger.error("Mode121_122_123_settings['SnapshotTime']")
        raise ValueError
    if not (Timeline_settings["StandardPointingAltitude"] <
            Mode121_122_123_settings["pointing_altitude"]
            and type(Mode121_122_123_settings["pointing_altitude"]) == int):
        Logger.error("Mode121_122_123_settings['pointing_altitude']")
        raise ValueError
    if not (0 < Mode121_122_123_settings["TimeSkip"]["TimeSkip"] <=
            2 * 3600 * 24 and
            (type(Mode121_122_123_settings["TimeSkip"]["TimeSkip"]) == int or
             type(Mode121_122_123_settings["TimeSkip"]["TimeSkip"]) == float)):
        Logger.error("Mode121_122_123_settings['TimeSkip']['TimeSkip']")
        raise ValueError
    if not (0 <= Mode121_122_123_settings["SnapshotSpacing"]
            and type(Mode121_122_123_settings["SnapshotSpacing"]) == int):
        Logger.error("Mode121_122_123_settings['SnapshotSpacing']")
        raise ValueError
    if not (Mode121_122_123_settings["SnapshotSpacing"] * 6 +
            Mode121_122_123_settings["SnapshotTime"] <
            Mode121_122_123_settings["freeze_duration"] <= 3600):
        Logger.error(
            "Mode121_122_123_settings['SnapshotSpacing'] * 5 + Mode121_122_123_settings['SnapshotTime'] > Mode121_122_123_settings['freeze_duration'] or Mode121_122_123_settings['freeze_duration'] > 3600"
        )
        raise ValueError
    if not (Mode121_122_123_settings["TimeToConsider"]["TimeToConsider"] <=
            Timeline_settings["duration"]["duration"]):
        Logger.error(
            "Mode121_122_123_settings['TimeToConsider']['TimeToConsider'] > Timeline_settings['duration']['duration']"
        )
        raise ValueError

    if not (Mode121_122_123_settings["SnapshotSpacing"] *
            (len(Mode121_settings["CCDSELs"]) - 1) +
            Mode121_122_123_settings["SnapshotTime"] <
            Mode121_122_123_settings["freeze_duration"] <= 3600):
        Logger.error(
            "Mode121_122_123_settings['SnapshotSpacing'] * (len(Mode121_settings['CCDSELs'])-1) + Mode124_settings['SnapshotTime'] > Mode121_122_123_settings['freeze_duration'] or Mode121_122_123_settings['freeze_duration'] > 3600"
        )
        raise ValueError

    if not (type(Mode121_settings["CCDSELs"]) == list):
        Logger.error("Mode121_settings['CCDSELs'] != list")
        raise TypeError
    for CCDSEL in Mode121_settings["CCDSELs"]:
        if not (CCDSEL in [1, 2, 4, 8, 16, 32]):
            Logger.error(
                "Mode121_settings['CCDSELs'] element != [1,2,4,8,16,32]")
            raise ValueError

    if not (type(Mode121_settings["start_date"]) == str):
        Logger.error("Mode121_settings['start_date']")
        raise TypeError
    if not (type(Mode122_settings["start_date"]) == str):
        Logger.error("Mode122_settings['start_date']")
        raise TypeError
    if not (type(Mode123_settings["start_date"]) == str):
        Logger.error("Mode123_settings['start_date']")
        raise TypeError

    if not (0 <= Mode122_settings["Exp_Time_IR"]
            and type(Mode122_settings["Exp_Time_IR"]) == int):
        Logger.error("Mode122_settings['Exp_Time_IR']")
        raise ValueError
    if not (0 <= Mode122_settings["Exp_Time_UV"]
            and type(Mode122_settings["Exp_Time_UV"]) == int):
        Logger.error("Mode122_settings['Exp_Time_UV']")
        raise ValueError
    if not (0 <= Mode123_settings["Exp_Time_IR"]
            and type(Mode123_settings["Exp_Time_IR"]) == int):
        Logger.error("Mode123_settings['Exp_Time_IR']")
        raise ValueError
    if not (0 <= Mode123_settings["Exp_Time_UV"]
            and type(Mode123_settings["Exp_Time_UV"]) == int):
        Logger.error("Mode123_settings['Exp_Time_UV']")
        raise ValueError

    if not (type(Mode121_settings["automatic"]) == bool):
        Logger.error("Mode121_settings['automatic']")
        raise TypeError
    if not (type(Mode122_settings["automatic"]) == bool):
        Logger.error("Mode122_settings['automatic']")
        raise TypeError
    if not (type(Mode123_settings["automatic"]) == bool):
        Logger.error("Mode123_settings['automatic']")
        raise TypeError

    if not (-60000 <= Mode124_settings["pointing_altitude"] <= 230000
            and type(Mode124_settings["pointing_altitude"]) == int):
        Logger.error("Mode124_settings['pointing_altitude']")
        raise ValueError
    if not (0 < Mode124_settings["timestep"] <= 10
            and type(Mode124_settings["timestep"]) == int):
        Logger.error("Mode124_settings['timestep']")
        raise ValueError
    if not (Mode124_settings["freeze_start"] >=
            Timeline_settings["pointing_stabilization"] +
            12 * Timeline_settings["CMD_separation"]
            and type(Mode124_settings["freeze_start"]) == int):
        Logger.error("Mode124_settings")
        raise TypeError
    if not (type(Mode124_settings["V_offset"]) == list):
        Logger.error("Mode124_settings['V_offset'] != list")
    for x in range(len(Mode120_settings["V_offset"])):
        if not (abs(Mode124_settings["V_offset"][x]) <= 1
                and 0 <= abs(Mode124_settings["H_offset"]) <= 10):
            Logger.error(
                "Mode124_settings['V_offset'] or Mode124_settings['H_offset']")
            raise ValueError
    if not (type(Mode124_settings["start_date"]) == str):
        Logger.error("Mode124_settings['start_date']")
        raise TypeError
    if not (type(Mode124_settings["automatic"]) == bool):
        Logger.error("Mode124_settings['automatic']")
        raise TypeError
    if not (0 < Mode124_settings["SnapshotTime"] <
            Mode124_settings["freeze_duration"] - 10
            and type(Mode124_settings["SnapshotTime"]) == int):
        Logger.error("Mode124_settings['SnapshotTime']")
        raise ValueError
    if not (Timeline_settings["StandardPointingAltitude"] <
            Mode124_settings["pointing_altitude"]
            and type(Mode124_settings["pointing_altitude"]) == int):
        Logger.error("Mode124_settings['pointing_altitude']")
        raise ValueError
    if not (0 <= Mode124_settings["SnapshotSpacing"]
            and type(Mode124_settings["SnapshotSpacing"]) == int):
        Logger.error("Mode124_settings['SnapshotSpacing']")
        raise ValueError
    if not (type(Mode124_settings["CCDSELs"]) == list):
        Logger.error("Mode124_settings['CCDSELs'] != list")
        raise TypeError
    if not (Mode124_settings["SnapshotSpacing"] *
            (len(Mode124_settings["CCDSELs"]) - 1) +
            Mode124_settings["SnapshotTime"] <
            Mode124_settings["freeze_duration"] <= 3600):
        Logger.error(
            "Mode124_settings['SnapshotSpacing'] * (len(Mode124_settings['CCDSELs'])-1) + Mode124_settings['SnapshotTime'] > Mode124_settings['freeze_duration'] or Mode124_settings['freeze_duration'] > 3600"
        )
        raise ValueError
    for CCDSEL in Mode124_settings["CCDSELs"]:
        if not (CCDSEL in [1, 2, 4, 8, 16, 32]):
            Logger.error(
                "Mode124_settings['CCDSELs'] element != [1,2,4,8,16,32]")
            raise ValueError
    if not (Mode124_settings["TimeToConsider"]["TimeToConsider"] <=
            Timeline_settings["duration"]["duration"]):
        Logger.error(
            "Mode124_settings['TimeToConsider'] > Timeline_settings['duration']"
        )
        raise ValueError

    MaximumNumberOfCMDsInMacro = 12

    if not (Timeline_settings["CMD_separation"] <=
            Mode130_settings["SnapshotSpacing"]
            and type(Mode130_settings["SnapshotSpacing"]) == int):
        Logger.error("Mode130_settings['SnapshotSpacing']")
        raise TypeError
    if not (Timeline_settings["pointing_stabilization"] +
            Timeline_settings["CMD_separation"] * MaximumNumberOfCMDsInMacro +
            Timeline_settings["mode_separation"] <
            Mode131_settings["mode_duration"]
            and type(Mode131_settings["mode_duration"]) == int):
        Logger.error("Mode131_settings['mode_duration'] is too short")
        raise TypeError
    if not (Timeline_settings["pointing_stabilization"] +
            Timeline_settings["CMD_separation"] * MaximumNumberOfCMDsInMacro +
            Timeline_settings["mode_separation"] <
            Mode134_settings["mode_duration"]
            and type(Mode134_settings["mode_duration"]) == int):
        Logger.error("Mode134_settings['mode_duration'] is too short")
        raise TypeError

    if not (-60000 <= Mode130_settings["pointing_altitude"] <= 230000
            and type(Mode130_settings["pointing_altitude"]) == int):
        Logger.error("Mode130_settings['pointing_altitude']")
        raise ValueError
    if not (-60000 <= Mode131_settings["pointing_altitude"] <= 230000
            and type(Mode131_settings["pointing_altitude"]) == int):
        Logger.error("Mode131_settings['pointing_altitude']")
        raise ValueError
    if not (-60000 <= Mode132_settings["pointing_altitude"] <= 230000
            and type(Mode132_settings["pointing_altitude"]) == int):
        Logger.error("Mode132_settings['pointing_altitude']")
        raise ValueError
    if not (-60000 <= Mode133_settings["pointing_altitude"] <= 230000
            and type(Mode133_settings["pointing_altitude"]) == int):
        Logger.error("Mode133_settings['pointing_altitude']")
        raise ValueError
    if not (-60000 <= Mode134_settings["pointing_altitude"] <= 230000
            and type(Mode134_settings["pointing_altitude"]) == int):
        Logger.error("Mode134_settings['pointing_altitude']")
        raise ValueError

    if not (type(Mode130_settings["start_date"]) == str):
        Logger.error("Mode130_settings['start_date']")
        raise TypeError
    if not (type(Mode131_settings["start_date"]) == str):
        Logger.error("Mode131_settings['start_date']")
        raise TypeError
    if not (type(Mode132_settings["start_date"]) == str):
        Logger.error("Mode132_settings['start_date']")
        raise TypeError
    if not (type(Mode133_settings["start_date"]) == str):
        Logger.error("Mode133_settings['start_date']")
        raise TypeError
    if not (type(Mode134_settings["start_date"]) == str):
        Logger.error("Mode134_settings['start_date']")
        raise TypeError

    if not (type(Mode132_settings["Exp_Times_IR"]) == list
            and type(Mode132_settings["Exp_Times_UV"]) == list):
        Logger.error(
            "Mode132_settings['Exp_Times_IR'] or Mode132_settings['Exp_Times_UV']"
        )
        raise TypeError
    if not (type(Mode133_settings["Exp_Times_IR"]) == list
            and type(Mode133_settings["Exp_Times_UV"]) == list):
        Logger.error(
            "Mode133_settings['Exp_Times_IR'] or Mode133_settings['Exp_Times_UV']"
        )
        raise TypeError

    if not (len(Mode132_settings["Exp_Times_IR"]) == len(
            Mode132_settings["Exp_Times_UV"])):
        Logger.error(
            "len(Mode132_settings['Exp_Times_IR']) != len(Mode132_settings['Exp_Times_UV'])"
        )
        raise TypeError
    if not (len(Mode133_settings["Exp_Times_IR"]) == len(
            Mode133_settings["Exp_Times_UV"])):
        Logger.error(
            "len(Mode133_settings['Exp_Times_IR']) != len(Mode133_settings['Exp_Times_UV'])"
        )
        raise TypeError

    # Check that CCDsync waits for long enough time for full CCD readout (standard)

    # Standard CCDsettings
    _, _, FullReadout_synctime, _ = Library.SyncArgCalculator(
        configFile.CCD_macro_settings("FullReadout"),
        Timeline_settings["CCDSYNC_ExtraOffset"],
        Timeline_settings["CCDSYNC_ExtraIntervalTime"],
    )
    _, _, CustomBinning_synctime, _ = Library.SyncArgCalculator(
        configFile.CCD_macro_settings("CustomBinning"),
        Timeline_settings["CCDSYNC_ExtraOffset"],
        Timeline_settings["CCDSYNC_ExtraIntervalTime"],
    )
    _, _, HighResUV_synctime, _ = Library.SyncArgCalculator(
        configFile.CCD_macro_settings("HighResUV"),
        Timeline_settings["CCDSYNC_ExtraOffset"],
        Timeline_settings["CCDSYNC_ExtraIntervalTime"],
    )

    _, _, HighResIR_syctime, _ = Library.SyncArgCalculator(
        configFile.CCD_macro_settings("HighResIR"),
        Timeline_settings["CCDSYNC_ExtraOffset"],
        Timeline_settings["CCDSYNC_ExtraIntervalTime"],
    )

    _, _, BinnedCalibration, _ = Library.SyncArgCalculator(
        configFile.CCD_macro_settings("BinnedCalibration"),
        Timeline_settings["CCDSYNC_ExtraOffset"],
        Timeline_settings["CCDSYNC_ExtraIntervalTime"],
    )

    max_normal_synctime = (max(
        max(FullReadout_synctime),
        max(CustomBinning_synctime),
        max(HighResUV_synctime),
        max(HighResIR_syctime),
        max(BinnedCalibration),
    ) / 1000)

    if not Timeline_settings["CCDSYNC_Waittime"] > max_normal_synctime:
        Logger.error("Timeline_settings['CCDSYNC_Waittime']")
        raise ValueError

    Logger.info("CheckConfigFile passed.")
def MinimalScienceXMLGenerator(configFile):
    """The Core function of *MinimalScienceXML_gen* part of *OPT*.

    The generated XML will: \n
        1. Run TC_pafCCDBIAS
        2. Run TC_pafCCDFlushBadColumns
        3. Run TC_pafCCDBadColumn
        4. Run Operational_Limb_Pointing_macro with CCD_macro equal to 'HighResIR'.

    The time between CMDs (CMD_separation) is fixed to 2 s. The start date is not set and needs to be added manually later.

    """

    "######## Try to Create a directory for storage of output files #######"
    try:
        os.mkdir("Output")
    except:
        pass

    "Reset temporary Globals"
    configFile.latestRelativeTime = 0
    configFile.current_pointing = None
    configFile.LargestSetTEXPMS = 0

    "############# Set up Logger #################################"
    Library.SetupLogger(configFile.Logger_name())

    "############# Get Settings from the Configuration File #########"
    CCDBIAS_settings = configFile.CCDBIAS_settings()
    CCDFlushBadColumns_settings = configFile.CCDFlushBadColumns_settings()
    CCDBadColumn_settings = configFile.CCDBadColumn_settings()
    Timeline_settings = configFile.Timeline_settings()
    CCD_settings = configFile.CCD_macro_settings("HighResIR")
    PM_settings = configFile.PM_settings()

    "######## SET CMD separation to 2 sec #################"
    Timeline_settings["CMD_separation"] = 2

    "################### XML-tree basis creator ####################################"

    root = etree.Element("InnoSatTimeline",
                         originator="OHB",
                         sdbVersion="9.5.99.2")

    root.append(etree.Element("description"))

    etree.SubElement(
        root[0],
        "timelineID",
        procedureIdentifier="",
        descriptiveName="MinimalScience",
        version="1.0",
    )

    etree.SubElement(root[0], "changeLog")
    etree.SubElement(
        root[0][1],
        "changeLogItem",
        version="1.0",
        date=str(datetime.date.today()),
        author="David Skanberg",
    )
    root[0][1][0].text = "The file was created using OPT"

    etree.SubElement(root[0], "validity")
    etree.SubElement(root[0][2], "startingDate")
    root[0][2][0].text = ""
    etree.SubElement(root[0][2], "scenarioDuration")
    root[0][2][1].text = ""

    etree.SubElement(root[0], "comment")
    root[0][3].text = (
        "This command sequence is a 'Minimum Science' Innosat timeline for MATS, created with OPT. Configuration File used: "
        + configFile.config_file_name)

    root.append(etree.Element("listOfCommands"))

    "####################### End of XML-tree basis creator #############################"

    "####################### Minimum Science CMDs ######################################"

    relativeTime = Commands.TC_pafCCDBIAS(
        root,
        relativeTime=1,
        CCDSEL=CCDBIAS_settings["CCDSEL"],
        VGATE=CCDBIAS_settings["VGATE"],
        VSUBST=CCDBIAS_settings["VSUBST"],
        VRD=CCDBIAS_settings["VRD"],
        VOD=CCDBIAS_settings["VOD"],
        Timeline_settings=Timeline_settings,
        configFile=configFile,
    )

    relativeTime = Commands.TC_pafCCDFlushBadColumns(
        root,
        relativeTime,
        CCDSEL=CCDFlushBadColumns_settings["CCDSEL"],
        Timeline_settings=Timeline_settings,
        configFile=configFile,
    )

    relativeTime = Commands.TC_pafCCDBadColumn(
        root,
        relativeTime,
        CCDSEL=CCDBadColumn_settings["CCDSEL"],
        NBC=CCDBadColumn_settings["NBC"],
        BC=CCDBadColumn_settings["BC"],
        Timeline_settings=Timeline_settings,
        configFile=configFile,
    )

    relativeTime = Macros.Operational_Limb_Pointing_macro(
        root,
        relativeTime,
        CCD_settings,
        PM_settings,
        pointing_altitude=Timeline_settings["StandardPointingAltitude"],
        Timeline_settings=Timeline_settings,
        configFile=configFile,
    )

    "Update duration in the Timeline"
    root[0][2][1].text = str(relativeTime +
                             Timeline_settings["mode_separation"])

    "####################### End of Minimum Science CMDs ################################"

    "### Write finished XML-tree with all commands to a file #######"
    XML_TIMELINE = os.path.join("Output", "XML_TIMELINE__MinimalScience_.xml")
    Logger.info("Write XML-tree to: " + XML_TIMELINE)
    f = open(XML_TIMELINE, "w")
    f.write(etree.tostring(root, pretty_print=True, encoding="unicode"))
    f.close()

    "Reset temporary Globals"
    configFile.latestRelativeTime = 0
    configFile.current_pointing = None
    configFile.LargestSetTEXPMS = 0

    logging.shutdown()
def XML_generator(configFile, SCIMOD_Path):
    """The core function of the XML_gen program.

    Reads a *Science Mode Timeline* .json file. Then chronologically goes though the *Science Mode Timeline*, calling for the *XML_generator_select* function.
    Any settings stated in the *Science Mode Timeline* will override any similar ones given in the set *Configuration File*.
    Also calls for XML_Initial_Basis_Creator to setup a XML tree which will be used to write CMDs to.

    Arguments:
        SCIMOD_Path (str): A string containing the path to the Science Mode Timeline .json file.
    Returns:
        None

    """

    ######## Try to Create a directory for storage of output files #######
    try:
        os.mkdir('Output')
    except:
        pass

    "Reset temporary Globals"
    configFile.latestRelativeTime = 0
    configFile.current_pointing = None
    configFile.LargestSetTEXPMS = 0

    ############# Set up Logger #################################
    Library.SetupLogger(configFile.Logger_name())

    Logger.info('Start of Program')
    Logger.info('')

    ################# Read Science Mode Timeline json file ############
    with open(SCIMOD_Path, "r") as read_file:
        SCIMOD = json.load(read_file)
    ################# Read Science Mode Timeline json file ############

    Logger.info('Science Mode Timeline Used: '+SCIMOD_Path)

    "Check if there are Timeline_settings given in the Science Mode Timeline"
    if(str(SCIMOD[0][0]) == 'Timeline_settings'):
        Timeline_settings_from_Timeline = SCIMOD[0][3]
        Timeline_settings = Library.dict_comparator(
            Timeline_settings_from_Timeline, configFile.Timeline_settings())
        Logger.info('Timeline_settings found in Science Mode Timeline. Using them')

        TLE_from_Timeline = SCIMOD[0][4]

    else:
        raise ValueError(
            'Timeline_settings not found in Science Mode Timeline. Using the ones in the chosen ConfigFile')

    #"Save the Timeline_settings to be used in a Global variable"
    #Globals.Timeline_settings = Timeline_settings

    ################ Get settings for Timeline from Config module ############
    timeline_duration = Timeline_settings['duration']['duration']
    Logger.info('timeline_duration: '+str(timeline_duration))

    timeline_start = ephem.Date(Timeline_settings['start_date'])
    Logger.info('timeline_start: '+str(timeline_start))

    ########    Call function to create XML-tree basis ##########################
    Logger.info('Call function XML_Initial_Basis_Creator')
    Logger.info('')
    root = XML_Initial_Basis_Creator(
        timeline_start, timeline_duration, SCIMOD_Path, configFile)

    ######## Loop through SCIMOD TIMELINE lIST, selecting one mode at a time #####
    Logger.info('Loop through Science Mode Timeline List')

    for x in range(len(SCIMOD)):

        Entry_Name = str(SCIMOD[x][0])
        StartDate = str(SCIMOD[x][1])
        EndDate = str(SCIMOD[x][2])
        Settings = SCIMOD[x][3]

        Logger.info('')
        Logger.info('Iteration number: '+str(x+1))
        Logger.info(Entry_Name)

        "Skip the first entry if it only contains Timeline_settings"
        if(str(SCIMOD[x][0]) == 'Timeline_settings'):
            continue

        Logger.debug('Start Date: '+StartDate)
        Logger.debug('End Date: '+EndDate)

        mode_duration = round((ephem.Date(EndDate) - ephem.Date(StartDate)) * 24*3600)
        relativeTime = round((ephem.Date(StartDate)-ephem.Date(timeline_start))*24*3600)
        Logger.debug('mode_duration: '+str(mode_duration))
        Logger.debug('relativeTime: '+str(relativeTime))

        if(relativeTime >= timeline_duration):
            Logger.warning('relativeTime is exceeding timeline_duration!!!')
            raise ValueError

        if(ephem.Date(StartDate) < timeline_start or mode_duration+relativeTime > timeline_duration):
            Logger.warning(
                Entry_Name+', is not scheduled within the boundaries of the timeline!!!')
            raise ValueError

        Logger.debug('Call XML_generator_select')
        XML_generator_select(root=root, duration=mode_duration, relativeTime=relativeTime,
                             name=Entry_Name, date=ephem.Date(StartDate), Settings=Settings,                       Timeline_settings=Timeline_settings, configFile=configFile)

    ### Rewrite path string to allow it to be in the name of the generated XML command file ###
    SCIMOD_Path = SCIMOD_Path.replace('\\', '_')
    SCIMOD_Path = SCIMOD_Path.replace('/', '_')
    SCIMOD_Path = SCIMOD_Path.replace('.json', '')

    ### Write finished XML-tree with all commands to a file #######
    XML_TIMELINE = os.path.join('Output', 'XML_TIMELINE__'+'FROM__'+SCIMOD_Path+'.xml')
    Logger.info('Write XML-tree to: '+XML_TIMELINE)
    f = open(XML_TIMELINE, 'w')
    f.write(etree.tostring(root, pretty_print=True, encoding='unicode'))
    f.close()

    statinfo = os.stat(XML_TIMELINE)
    SizeOfXML = statinfo.st_size
    DataLimitInBytes = 20*10**6
    if(SizeOfXML > DataLimitInBytes):
        input('Size of XML Timeline file exceeds allowed datalimit (20Mb). Press enter to acknowledge')

    "Reset temporary Globals"
    configFile.latestRelativeTime = 0
    configFile.current_pointing = None
    configFile.LargestSetTEXPMS = 0
    logging.shutdown()

    return XML_TIMELINE