MAY 27/Integration & Testing/4 MIN READ

Nothing in the ICD Says Which Message Is Legal Now

Dan Zaidenband

Share on

Nothing in the ICD Says Which Message Is Legal Now

Every command the ground sent to SOHO on 24 June 1998 was legal. Each one matched the interface, arrived in the right format, and was accepted by the spacecraft. By the small hours of the next morning the observatory was tumbling, silent and freezing, and no interface document on the program could have explained why.

The loss is usually filed under operations: bad procedures, a tired team, a long night. We read it as an interface failure, and a common one. Two systems exchanged well-formed messages whose meaning depended on a state that neither message carried and no document defined.

Read an ICD end to end and you learn the vocabulary of an interface. Every message that may cross it, every field, type, unit, range and rate. You learn close to nothing about the grammar: which messages are legal when, what changes when the system moves from one mode to another, who may command the move, and what happens to the messages already in flight. That grammar exists. It runs in both boxes. It is written in two places, in two languages, by two teams who have never compared their copies.

Every number is read against a state

In the hours before the loss, a ground procedure left one of SOHO's gyros with its gain set twenty times too high. The gyro was healthy. Its telemetry was correct for the configuration it was in. The roll rate on the ground display read twenty times the real value, and the flight operations team, reading it against the configuration they believed was active, diagnosed a failed gyro and switched it off. The number was right. The state assumed while reading it was wrong.

The gyro was telling the truth. The ground was listening in a mode that had ended.

The packet carried no field naming its own gain setting. There was nowhere in the interface to put one, because the interface had been defined as a set of measurements rather than as measurements plus the configuration that makes them mean anything. That is not a SOHO peculiarity. Gain settings, filter states, calibration tables selected in flight, redundancy side A or B, coarse versus fine pointing: each one changes what a field means, and almost none of them travel with the field.

The dangerous mode is the one you are not in yet

The first SOHO error is stranger and more useful. A command script despun a second gyro. That part was intended. The version of the sequence used that day, an option added to the ground system in early 1997 and never run before, despun the gyro but did not re-enable the onboard function that would spin it back up.

Nothing broke. The spacecraft was in a mode that did not need that gyro. The dependency mattered only later, when a pointing error crossed five degrees and the spacecraft entered Emergency Sun Reacquisition, which did need it.

So the precondition for an emergency mode was set hours earlier, by a procedure whose author was thinking about a different mode. No interface on the program stated the dependency: in ESR, this gyro must be spinning, therefore any sequence that despins it must leave the respin enable set. That sentence is exactly the kind of thing an ICD could hold and never does.

Most systems carry the same shape of risk. The rarely entered modes are the dangerous ones. Safe mode, contingency, degraded, failover, the side B configuration you have run once on the bench: these have the fewest documented entry conditions, the least simulation time, and the most preconditions established by unrelated code that ran hours or years earlier.

A mode table is not a state machine

Programs do write mode tables. Somewhere in the design description there is a page listing standby, coarse point, fine point, safe, and a paragraph of prose about each. That page is not an interface definition, and the difference is the whole argument.

A mode table does not say which transitions are legal from which state. It does not say what triggers each one, or which commands a subsystem must reject in each mode and how it reports the rejection. It says nothing about the message that was in flight when the mode changed, or about whether the two sides transition at the same instant or one leads the other. Integration answers those questions by experiment, one surprise at a time, and the answers end up in test reports rather than in the interface.

The consumer side is worse. Every consumer of an interface carries a model of the producer's state, because it has to decide what to do with what arrives. That model gets built from one engineer's reading of the ICD and a couple of clarifying emails. Two subsystems then run two different models of each other, and neither model is reviewed, because neither is written down anywhere a reviewer could find it.

Where the consequence is obvious, someone writes it down

The interface for a vehicle approaching the International Space Station is a state machine, and it is published as one. There is a 4 by 2 by 2 km approach ellipsoid and a 200 m keep-out sphere. Vehicles hold at defined points. The collision avoidance rules are stated per region: a maneuver started outside the keep-out sphere may not carry the vehicle into it, and one started inside must begin by establishing a positive opening rate. A departing vehicle has to be clear of the ellipsoid within 90 minutes. Regions, transitions, and what each side owes the other in every one of them.

That interface works. Vehicles from several companies, running unrelated flight software, have flown it for years. It is also one interface out of hundreds on each of those programs, and it happens to be the one with seven people on the far side of the hatch. Every other interface on the same vehicle is described the ordinary way, as a list of messages.

Put the state machine in the interface definition

If the interface lives in code rather than in a document, the state machine fits in it, and both the flight software and the simulator can be generated from the same source.

interface: adcs_to_obc
version: 4.2
states: [standby, coarse_point, fine_point, esr]

message: attitude_rate
  fields:
    rate_xyz:     {type: float32[3], units: rad/s}
    gain_state:   {type: enum[low, high]}   # what makes rate_xyz mean something
    source_state: {type: enum[standby, coarse_point, fine_point, esr]}
  legal_in: [coarse_point, fine_point, esr]
  rate_hz:  {coarse_point: 1, fine_point: 10, esr: 10}

transition: any -> esr
  trigger: sun_pointing_error > 5 deg
  requires:
    - gyro_a.respin_enable == true
  on_entry:
    obc_shall

interface: adcs_to_obc
version: 4.2
states: [standby, coarse_point, fine_point, esr]

message: attitude_rate
  fields:
    rate_xyz:     {type: float32[3], units: rad/s}
    gain_state:   {type: enum[low, high]}   # what makes rate_xyz mean something
    source_state: {type: enum[standby, coarse_point, fine_point, esr]}
  legal_in: [coarse_point, fine_point, esr]
  rate_hz:  {coarse_point: 1, fine_point: 10, esr: 10}

transition: any -> esr
  trigger: sun_pointing_error > 5 deg
  requires:
    - gyro_a.respin_enable == true
  on_entry:
    obc_shall

interface: adcs_to_obc
version: 4.2
states: [standby, coarse_point, fine_point, esr]

message: attitude_rate
  fields:
    rate_xyz:     {type: float32[3], units: rad/s}
    gain_state:   {type: enum[low, high]}   # what makes rate_xyz mean something
    source_state: {type: enum[standby, coarse_point, fine_point, esr]}
  legal_in: [coarse_point, fine_point, esr]
  rate_hz:  {coarse_point: 1, fine_point: 10, esr: 10}

transition: any -> esr
  trigger: sun_pointing_error > 5 deg
  requires:
    - gyro_a.respin_enable == true
  on_entry:
    obc_shall

The requires line is the SOHO finding written as an interface constraint instead of as a lesson learned. A ground procedure that clears respin_enable now fails a check before anyone uplinks it, rather than two and a half years into the mission. The source_state field means a consumer never has to guess which machine produced the number in front of it.

Then run the transitions. Enter every rarely used mode in simulation, from every state that can precede it, with the ground procedures that will be live at the time. Mode entry is cheap to exercise in software-in-the-loop and close to impossible to exercise on a spacecraft.

Accepting this changes what "interface complete" means. A complete interface is not a full field list with the TBD count at zero. It is something you can execute: states enumerated, transitions triggered and gated, each message stamped with the state that produced it. Most programs are further from that than their ICD status reports suggest, because those reports count fields.

SOHO was found by radar five weeks after it went quiet, recovered over the following month, and is still returning data thirty years after launch. The recovery is the good story. The failure is the better lesson, because for hours two systems exchanged correct messages and destroyed a working spacecraft between them. Correctness was never a property of the message. It was a property of the message and the state, and only one of those is in your ICD.

Share on

More from the blog