AttitudesSequence.registerSwitchEvents seems still necessary in Orekit 13

Hi all

I recently updated to Orekit 13.1 from 12.x, and my existing code using AttitudesSequence no longer works as expected.

Specifically, no switch events occur.

The only change I made was removing the registerSwitchEvents call, following the guideline below:

Has anyone else encountered this issue?

Hi there,

How do you use the sequence?
Is it with custom detectors or with native Orekit ones?
Do you use the STM propagation system alongside? Do you use it on a NumericalPropagator or something else like Ephemeris?

Ideally we would need runnable code to reproduce the issue.

Cheers,
Romain.

Hi Serrof

The sample code below generates an error on line 186 because Orekit 13 removes the registerSwitchEvents method.

To resolve this, the line should be commented out.

The code works correctly in Orekit 12.2.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Aug 12 23:43:29 2025
"""
from orekit import initVM, JArray_double
from orekit.pyhelpers import setup_orekit_curdir, absolutedate_to_datetime

from org.orekit.attitudes import AlignedAndConstrained, AttitudesSequence, LofOffset, PredefinedTarget
from org.orekit.bodies import CelestialBodyFactory, OneAxisEllipsoid
from org.orekit.forces.gravity.potential import GravityFieldFactory
from org.orekit.frames import FramesFactory, LOFType
from org.orekit.orbits import KeplerianOrbit, PositionAngleType
from org.orekit.propagation import SpacecraftState, PropagationType, Propagator
from org.orekit.propagation.events import EclipseDetector
from org.orekit.propagation.events.handlers import PythonEventHandler
from org.orekit.propagation.sampling import PythonOrekitFixedStepHandler
from org.orekit.propagation.semianalytical.dsst import DSSTPropagator
from org.orekit.propagation.semianalytical.dsst.forces import DSSTTesseral, DSSTThirdBody, DSSTZonal
from org.orekit.time import AbsoluteDate, TimeScalesFactory
from org.orekit.utils import AngularDerivativesFilter, Constants, IERSConventions, PVCoordinatesProvider

from org.hipparchus.geometry import Vector
from org.hipparchus.geometry.euclidean.threed import Vector3D
from org.hipparchus.ode.events import Action
from org.hipparchus.ode.nonstiff import DormandPrince853Integrator

import matplotlib.pyplot as plt

import os
import sys

from math import radians, degrees
import numpy as np

here = os.getcwd()
up = os.path.dirname(here)

cond1 = os.path.basename(here) == 'python'
cond2 = os.path.basename(up) == 'python'
if cond1:
    data = os.path.join(here, 'orekit-data-master.zip')
    if not here in sys.path:
        sys.path.append(here)
elif cond2:
    data = os.path.join(up, 'orekit-data-master.zip')
    if not up in sys.path:
        sys.path.append(up)
    
initVM()
setup_orekit_curdir(data)


class EclipseEventHandler(PythonEventHandler):
    
    def __init__(self):
        self.eclipse_in = []
        self.eclipse_out = []
        super().__init__()
    
    def init(self, s, target, detector):
        pass
    
    def eventOccurred(self, s, detector, increasing):
        if increasing: # sunlit
            self.eclipse_out.append(s.getDate())
        
        else: # eclipse
            self.eclipse_in.append(s.getDate())

        return Action.CONTINUE
    
    def finish(self, final_state, detector):
        pass

class DefaultOrbitHandler(PythonOrekitFixedStepHandler):
    def __init__(self):
        self.state = []
        self.time = []
        self.sun_vec = []
        super().__init__()
    
    def init(self, s0, t, step):
        pass
        
    def handleStep(self, s):
        
        eci2body = s.getAttitude().getRotation()
        
        r_sun_eci = PVCoordinatesProvider.cast_(SUN).getPVCoordinates(s.getDate(), ECI).getPosition()
        
        e_r_rel_eci = Vector.cast_(r_sun_eci.subtract(s.getPosition())).normalize()
        e_r_rel_body = eci2body.applyTo(e_r_rel_eci)
        
        self.time.append(s.getDate())
        self.state.append(s)
        self.sun_vec.append(e_r_rel_body)

    def finish(self, s):
        pass


#%% Earth frames

ECI = FramesFactory.getEME2000()
ECEF = FramesFactory.getITRF(IERSConventions.IERS_2003, True)
ELLIPSOID = OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, Constants.WGS84_EARTH_FLATTENING, ECEF)

#%% Perturbations for mean elements
forces = []

# Gravity
gravity = GravityFieldFactory.getUnnormalizedProvider(30, 0)
zonal_force = DSSTZonal(gravity)
tesseral_force = DSSTTesseral(ECEF, Constants.WGS84_EARTH_ANGULAR_VELOCITY, gravity)

[forces.append(gf) for gf in [zonal_force, tesseral_force]]

# Third bodies
SUN = CelestialBodyFactory.getSun()
MOON = CelestialBodyFactory.getMoon()

[forces.append(DSSTThirdBody(b, b.getGM())) for b in [SUN, MOON]]

#%% Initial states
# Target orbit
INITIAL_DATE = AbsoluteDate(2025, 1, 1, 10, 30, 0., TimeScalesFactory.getUTC())
SMA0 = 6879376.415393126
ECC0 = 0.0012533557095235504
INC0 = 1.6997385137705967
AOP0 = 1.5707963267948966
RAAN0 = 4.51186235457749
TA0 = -1.5707963267948966
INITIAL_ORBIT = KeplerianOrbit(SMA0, ECC0, INC0, AOP0, RAAN0, TA0, PositionAngleType.TRUE,
                              ECI, INITIAL_DATE, Constants.EIGEN5C_EARTH_MU)

# Attitude
ac_day_law = AlignedAndConstrained(Vector3D.MINUS_K, PredefinedTarget.SUN, 
                                   Vector3D.MINUS_J, PredefinedTarget.MOMENTUM, 
                                   SUN, ELLIPSOID)
ac_night_law = AlignedAndConstrained(Vector3D.PLUS_K, PredefinedTarget.EARTH,
                                     Vector3D.MINUS_J, PredefinedTarget.MOMENTUM,
                                     SUN, ELLIPSOID)

INITIAL_STATE = SpacecraftState(INITIAL_ORBIT)

#%% Propagation
# Time
OUT_STEP = 60.
DURATION = 86400. 

# Event detectors and step handlers
eclipse_handler = EclipseEventHandler()
eclipse_detector = EclipseDetector(SUN, Constants.IAU_2015_NOMINAL_SOLAR_RADIUS, ELLIPSOID).withHandler(eclipse_handler)
orbit_handler = DefaultOrbitHandler()

# Attitudes sequence
attitudes_seq = AttitudesSequence()
attitudes_seq.addSwitchingCondition(ac_day_law, ac_night_law, eclipse_detector, False, True, 60., AngularDerivativesFilter.USE_RRA, None)
attitudes_seq.addSwitchingCondition(ac_night_law, ac_day_law, eclipse_detector, True, False, 60., AngularDerivativesFilter.USE_RRA, None)

# attitudes_seq = AttitudesSwitcher()
# attitudes_seq.addSwitchingCondition(ac_day_law, ac_night_law, eclipse_detector, False, True, None)
# attitudes_seq.addSwitchingCondition(ac_night_law, ac_day_law, eclipse_detector, True, False, None)

if eclipse_detector.g(INITIAL_STATE) >= 0:
    # initial position is in daytime
    attitudes_seq.resetActiveProvider(ac_day_law)
else:
    # initial position is in nighttime
    attitudes_seq.resetActiveProvider(ac_night_law)

# Propagator
tolerances = DSSTPropagator.tolerances(1e-10, INITIAL_ORBIT)
integrator = DormandPrince853Integrator(1e-6, 86400., 
                                        JArray_double.cast_(tolerances[0]), JArray_double.cast_(tolerances[1]))
dsst_prop = DSSTPropagator(integrator)
[dsst_prop.addForceModel(f) for f in forces]
dsst_prop.addEventDetector(eclipse_detector)
Propagator.cast_(dsst_prop).setStepHandler(OUT_STEP, orbit_handler)
dsst_prop.setInitialState(INITIAL_STATE, PropagationType.MEAN)

dsst_prop.setAttitudeProvider(attitudes_seq)
attitudes_seq.registerSwitchEvents(dsst_prop)

dsst_prop.propagate(INITIAL_DATE, INITIAL_DATE.shiftedBy(DURATION))

#%% Results

sun_angle = [degrees(Vector3D.angle(Vector3D.MINUS_K, sv)) for sv in orbit_handler.sun_vec]

time = [absolutedate_to_datetime(t) for t in orbit_handler.time]

eclipse_in = [absolutedate_to_datetime(t) for t in eclipse_handler.eclipse_in]
eclipse_out = [absolutedate_to_datetime(t) for t in eclipse_handler.eclipse_out]
if eclipse_detector.g(INITIAL_STATE) < 0:
    eclipse_in.insert(0, time[0])

if len(eclipse_in) > len(eclipse_out):
    eclipse_out.append(time[-1])


plt.close('all')

_, axs = plt.subplots(1, sharex=True)
axs.plot(time, sun_angle)
axs.set_xlabel('Time')
axs.set_ylabel('Sun Angle (deg)')
[axs.axvspan(s, e, alpha=0.3, color='grey') for s, e in zip(eclipse_in, eclipse_out)]
axs.grid()

Please review the result plots below:

  • The first plot is generated using Orekit 12.2.

  • The second plot is generated using Orekit 13.1.

As shown, without registerSwitchEvents in Orekit 13.1, the attitude does not switch, resulting in a constant sun angle of zero.

Thanks a bunch.
Unfortunately on my side I won’t be able to run it this week, but maybe somebody else will.
Quick question tho, does it work with Orekit 13.0.X ?

Cheers,
Romain

Thank you Romain.

I have just checked 13.0.1, but the switch doesn’t work correctly.

Hello everyone,

I can confirm that this issue is related to the DSSTPropagator as it worked correctly with a NumericalPropagator. Unfortunatly i cannot investigate much further due to time issues.

You will find below the java version of @moonshot code for debug:

import org.hipparchus.geometry.euclidean.threed.Vector3D;
import org.hipparchus.ode.events.Action;
import org.hipparchus.ode.nonstiff.DormandPrince853Integrator;
import org.orekit.attitudes.AlignedAndConstrained;
import org.orekit.attitudes.AttitudeProvider;
import org.orekit.attitudes.AttitudesSequence;
import org.orekit.attitudes.PredefinedTarget;
import org.orekit.bodies.CelestialBody;
import org.orekit.bodies.CelestialBodyFactory;
import org.orekit.bodies.OneAxisEllipsoid;
import org.orekit.data.DataContext;
import org.orekit.data.DataProvidersManager;
import org.orekit.data.DirectoryCrawler;
import org.orekit.forces.gravity.potential.GravityFieldFactory;
import org.orekit.frames.Frame;
import org.orekit.frames.FramesFactory;
import org.orekit.orbits.KeplerianOrbit;
import org.orekit.orbits.Orbit;
import org.orekit.orbits.PositionAngleType;
import org.orekit.propagation.SpacecraftState;
import org.orekit.propagation.events.EclipseDetector;
import org.orekit.propagation.events.EventDetector;
import org.orekit.propagation.events.handlers.EventHandler;
import org.orekit.propagation.numerical.NumericalPropagator;
import org.orekit.propagation.sampling.OrekitFixedStepHandler;
import org.orekit.propagation.semianalytical.dsst.DSSTPropagator;
import org.orekit.propagation.semianalytical.dsst.forces.DSSTTesseral;
import org.orekit.propagation.semianalytical.dsst.forces.DSSTThirdBody;
import org.orekit.propagation.semianalytical.dsst.forces.DSSTZonal;
import org.orekit.time.AbsoluteDate;
import org.orekit.time.TimeScalesFactory;
import org.orekit.utils.AngularDerivativesFilter;
import org.orekit.utils.Constants;
import org.orekit.utils.IERSConventions;
import org.orekit.utils.PVCoordinatesProvider;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

public class AttitudesSequenceRegister {

    // Storage equivalent to your Python lists
    static class EclipseEventHandler implements EventHandler {
        final List<AbsoluteDate> eclipseIn  = new ArrayList<>();
        final List<AbsoluteDate> eclipseOut = new ArrayList<>();

        @Override
        public Action eventOccurred(SpacecraftState s,
                                    EventDetector detector,
                                    boolean increasing) {
            if (increasing) {
                // exiting eclipse -> sunlight
                eclipseOut.add(s.getDate());
            } else {
                // entering eclipse
                eclipseIn.add(s.getDate());
            }
            return Action.CONTINUE;
        }
    }

    static class DefaultOrbitHandler implements OrekitFixedStepHandler {
        final List<SpacecraftState> states     = new ArrayList<>();
        final List<AbsoluteDate>    times      = new ArrayList<>();
        final List<Vector3D>        sunVecBody = new ArrayList<>();

        private final PVCoordinatesProvider sun;
        private final Frame                 eci;

        DefaultOrbitHandler(PVCoordinatesProvider sun,
                            Frame eci) {
            this.sun = sun;
            this.eci = eci;
        }

        @Override
        public void init(SpacecraftState s0,
                         AbsoluteDate t,
                         double step) {
        }

        @Override
        public void handleStep(SpacecraftState s) {
            // ECI -> body rotation
            var eciToBody = s.getAttitude().getRotation();

            // Sun position (ECI) and relative direction
            Vector3D rSunEci = sun.getPVCoordinates(s.getDate(), eci).getPosition();
            Vector3D eRelEci = rSunEci.subtract(s.getPosition()).normalize();

            // Express in body frame
            Vector3D eRelBody = eciToBody.applyTo(eRelEci);

            times.add(s.getDate());
            states.add(s);
            sunVecBody.add(eRelBody);
        }

        @Override
        public void finish(SpacecraftState finalState) {
        }
    }

    public static void main(String[] args) throws Exception {

        // ------------------------------------------------------------
        // Orekit data (adjust path if needed)
        // ------------------------------------------------------------
        final File                 orekitData = new File(System.getProperty("user.home"), "orekit-data");
        final DataProvidersManager dpm        = DataContext.getDefault().getDataProvidersManager();
        dpm.addProvider(new DirectoryCrawler(orekitData));

        // ------------------------------------------------------------
        // Frames and bodies
        // ------------------------------------------------------------
        final Frame ECI  = FramesFactory.getEME2000();
        final Frame ECEF = FramesFactory.getITRF(IERSConventions.IERS_2003, true);

        final OneAxisEllipsoid EARTH = new OneAxisEllipsoid(Constants.WGS84_EARTH_EQUATORIAL_RADIUS, Constants.WGS84_EARTH_FLATTENING, ECEF);

        final CelestialBody SUN  = CelestialBodyFactory.getSun();
        final CelestialBody MOON = CelestialBodyFactory.getMoon();

        // ------------------------------------------------------------
        // Initial orbit (same values as Python)
        // ------------------------------------------------------------
        final AbsoluteDate INITIAL_DATE = new AbsoluteDate(2025, 1, 1, 10, 30, 0.0, TimeScalesFactory.getUTC());
        final double       SMA0         = 6_879_376.415393126;
        final double       ECC0         = 0.0012533557095235504;
        final double       INC0         = 1.6997385137705967;
        final double       AOP0         = 1.5707963267948966;
        final double       RAAN0        = 4.51186235457749;
        final double       TA0          = -1.5707963267948966;

        final Orbit INITIAL_ORBIT = new KeplerianOrbit(SMA0, ECC0, INC0, AOP0, RAAN0, TA0, PositionAngleType.TRUE, ECI, INITIAL_DATE, Constants.EIGEN5C_EARTH_MU);

        SpacecraftState INITIAL_STATE = new SpacecraftState(INITIAL_ORBIT);

        // ------------------------------------------------------------
        // Attitudes: day (Sun pointing) and night (nadir pointing),
        // with momentum constraint like in Python.
        // ------------------------------------------------------------
        final AttitudeProvider acDay =
                new AlignedAndConstrained(Vector3D.MINUS_K, PredefinedTarget.SUN,
                                          Vector3D.MINUS_J, PredefinedTarget.MOMENTUM, SUN, EARTH);
        final AttitudeProvider acNight =
                new AlignedAndConstrained(Vector3D.PLUS_K, PredefinedTarget.EARTH,
                                          Vector3D.MINUS_J, PredefinedTarget.MOMENTUM, SUN, EARTH);

        // ------------------------------------------------------------
        // DSST forces (zonal + tesseral + third bodies)
        // ------------------------------------------------------------
        var gravity   = GravityFieldFactory.getUnnormalizedProvider(30, 0);
        var zonal     = new DSSTZonal(gravity);
        var tess      = new DSSTTesseral(ECEF, Constants.WGS84_EARTH_ANGULAR_VELOCITY, gravity);
        var thirdSun  = new DSSTThirdBody(SUN, SUN.getGM());
        var thirdMoon = new DSSTThirdBody(MOON, MOON.getGM());

        // ------------------------------------------------------------
        // Eclipse detector and handlers
        // ------------------------------------------------------------
        EclipseEventHandler eclipseHandler = new EclipseEventHandler();
        EclipseDetector eclipseDetector = new EclipseDetector(SUN, Constants.IAU_2015_NOMINAL_SOLAR_RADIUS, EARTH)
                .withHandler(eclipseHandler);

        DefaultOrbitHandler fixedStepHandler = new DefaultOrbitHandler(SUN, ECI);

        // ------------------------------------------------------------
        // Attitudes sequence with switching on eclipse
        // ------------------------------------------------------------
        AttitudesSequence seq = new AttitudesSequence();

        // Day -> Night (entering eclipse: increasing == false)
        seq.addSwitchingCondition(acDay, acNight, eclipseDetector,
                                  false,
                                  true,
                                  60.0,
                                  AngularDerivativesFilter.USE_RRA,
                                  (prev, next, state) -> System.out.println("DAY TO NIGHT"));

        // Night -> Day (exiting eclipse: increasing == true)
        seq.addSwitchingCondition(acNight, acDay, eclipseDetector,
                                  true,
                                  false,
                                  60.0,
                                  AngularDerivativesFilter.USE_RRA,
                                  (prev, next, state) -> System.out.println("NIGHT TO DAY"));

        // Choose initial attitude based on eclipse sign at t0
        if (eclipseDetector.g(INITIAL_STATE) >= 0.0) {
            seq.resetActiveProvider(acDay);   // daylight at t0
        } else {
            seq.resetActiveProvider(acNight); // eclipse at t0
        }

        // ------------------------------------------------------------
        // Propagator (DSST, osculating out, mean initial state)
        // ------------------------------------------------------------
        double[][]                 tolerances = DSSTPropagator.tolerances(1e-10, INITIAL_ORBIT);
        DormandPrince853Integrator integ      = new DormandPrince853Integrator(1.0e-6, 86400.0, tolerances[0], tolerances[1]);

        NumericalPropagator dsst = new NumericalPropagator(integ);
        dsst.setAttitudeProvider(seq);

//        dsst.addForceModel(zonal);
//        dsst.addForceModel(tess);
//        dsst.addForceModel(thirdSun);
//        dsst.addForceModel(thirdMoon);

        dsst.addEventDetector(eclipseDetector);

        // Fixed-step sampling (60 s)
        final double OUT_STEP = 60.0;
        // Orekit ≥ 11: use the multiplexer to register handlers
        dsst.getMultiplexer().add(OUT_STEP, fixedStepHandler);

        // Provide mean elements as initial state (like Python setInitialState(..., PropagationType.MEAN))
        dsst.setInitialState(INITIAL_STATE);

        // ------------------------------------------------------------
        // Propagate
        // ------------------------------------------------------------
        final double DURATION = 86400.0; // 1 day
        AbsoluteDate t0       = INITIAL_DATE;
        AbsoluteDate t1       = INITIAL_DATE.shiftedBy(DURATION);

        SpacecraftState finalState = dsst.propagate(t0, t1);

        // (Optional) print a tiny summary
        System.out.println("Final date: " + finalState.getDate());
        System.out.println("# samples: " + fixedStepHandler.times.size());
        System.out.println("# eclipse in  events: " + eclipseHandler.eclipseIn.size());
        System.out.println("# eclipse out events: " + eclipseHandler.eclipseOut.size());
    }
}

Cheers,
Vincent

Thanks for this Vincent. I must have only added the internal detectors in NumericalPropagator. That should be an easy fix.
@moonshot can you create an issue on the forge?
This should be included in the next patch I think.

Edit: in the meantime, you can try to manually add the detectors to the Dsst propagator with the output of getEventDetectors() on the AttitudeSequence

Cheers,
Romain.

Thank you @Serrof @Vincent!

It works now!

For those experiencing this issue with the Orekit Python wrapper (JCC),

[dsst_prop.addEventDetector(det) for det in attitudes_seq.getEventDetectors().toArray()]
1 Like

Good job @moonshot, i was currently struggling to convert the Stream object to a python list :sweat_smile: . Thank you for providing the solution. I will create an issue on the forge so don’t bother with it @moonshot unless you want to.

Thank you @Serrof for the workaround :+1:

Cheers,
Vincent

Issue created: AtittudeProvider events are not registered automatically with DSSTPropagator (#1788) · Issues · Orekit / Orekit · GitLab

Thanks Vincent.
I’m afraid it’s a more generic problem tho, as event detectors from any AtittudeProvider won’t be included in DSST propagation.

1 Like

You are right, issue name updated

Hello everyone!

I would like to add to this discussion my own use case, which seems to also be affected by the removal of the registerSwitchEvents method.

What I was trying to achieve was simulating an observation mission, where my spacecraft had an attitude during the observation itself, and another when it was not observing.

Since this stand-by attitude was actually two different attitudes, one during nighttime and another during daytime, I built it as an AttitudesSequence with an EclipseDetector as switch. I then tried to create another AttitudesSequence, which included my observation attitude and stand-by attitude, with a DateDetector as switch.

I was originally implementing it in Orekit 13 and I was not successful. I then stumbled upon this topic and tried implementing it in Orekit 12, and it worked! I just had to use the registerSwitchEvents method of both my AttitudesSequence.

It seems to me that only the “top-level” SwitchEvents are registered on this new implementation, that is, only the SwitchEvents of the AttitudeProvider which is used as argument of the Propagator.setAttitudeProvider() method are registered.

Please find here two tests for both versions, and some graphics which show the problem I stumbled on.

Test Case in Orekit 12: TestCase12.java (9.8 KB)

Test Case in Orekit 13: TestCase13.java (9.7 KB)

The following graphics show what I was expecting to obtain, and obtain with Orekit 12, and the same result using the Orekit 13 implementation:

Thank you very much!

Hi there,

I’ve not looked at your code yet but I can already tell you that the AttitudesSequence was designed to be the single provider. You should be able to implement all transitions with a single object.

Cheers,
Romain.

Hello!

Thank you for the fast response!

At least for this specific use case, I find it hard to implement using a standalone AttitudesSequence, that is, without including a AttitudesSequence inside another AttitudesSequence.

As the observation event, in this case, is triggered and finished depending on time passed, the implementation with two “nested” AttitudesSequence allows for a seamless transition as I do not need to calculate whether the transition will be to night or day stand-by attitude, as the embedded stand-by AttitudesSequence has already handled it. I am also not sure how I could handle a transition that is dependant on both a date and a previous event happening.

In the end, I feel like even if not intended, this is a feature which comes really in handy when dealing with different attitudes and their transitions, and would allow to reuse AttitudesSequence’s as blocks, as I can easily change the observation attitude without having to change any switching condition or event.

Thanks once again for your attention!

To be sure to understand, you’re registering transitions with two different instances of AttitudesSequence and you’re passing one of them to setAttitudeProvider?
In that case I’m not sure that your propagation really uses the second one, or if it does it’s really not how things were intented to be. Anyhow it’s definitely a loophole you found.

If half of your transitions are purely based on DateDetector, you could maybe use an AggregateBoundedAttitudeProvider, delegating to an AttitudesSequence and other providers?

Have you tried using a sequence inside a sequence in Orekit 13?
Edit: again, as a workaround, try adding the switch detectors from the second sequence manually to the propagator

Cheers,
Romain.

Using a sequence inside a sequence is exactly what I am doing. I then pass the “main” sequence, which has another sequence inside, as the AttitudeProvider to the propagator.

My problem here is that in Orekit 13 the Switch detectors of the sequence which is inside another sequence are not triggered, when in Orekit 12, they are.

I am not sure that an AggregateBoundedAttitudeProvider would help, as I would need to handle the transitions to, in my case, either a day or night attitude on stand-by. Although I could add a sequence to this AggregateBoundedAttitudeProvider, I am not sure the events would trigger either way. I cannot test right now but whenever I can I will.

Regarding the workaround, I will also give it a try when I can. Would sequences inside sequences be something you would be open to support, as in Orekit 12?

Thanks once again!

Keep me posted I believe the workaround should do the job.
It should also be really easy to internally add them so that could be in a patch just like the original issue of the thread

I am not sure I understand why you need a sequence within a sequence.
The attitude sequences are basically finite state automatas, with attitude modes as nodes and as many transitions from any node to any other node using event detectors.
The event detectors are considered only with respect to the active node.
Can’t you set up a single automata with all the nodes and all the transitions you need handled by this automata?

Hello!

My problem with a single automata would be a transition between states which depends on two events, which do not occur at the same time.

In the case above, I would go from the observation attitude to day attitude if the observation time has already passed and if the eclipse detector has had as last event an entering in daytime. Similarly, the observation attitude would transition to nighttime attitude if the observation time has already passed and if the eclipse detector last detected a nighttime entry.

When using the sequence inside sequence approach, by stating a Switch condition from observation to stand-by (which is what I am calling the sequence of nightime → daytime → nightime → …) it automatically switches to either nightime or daytime, whichever it is supposed to transition to, as the stand-by Events have triggered while I was in the observation attitude, and updated the stand-by attitude. Summed up, it would be something like the following image:

Due to the limitation on embedded images, please refer to the following comment