> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hardwave.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Component Faults and Health

> Runtime warnings, faults, and degraded behaviour during simulation in Hardwave

Hardwave components can emit **runtime diagnostics** when operating limits are exceeded (motor thermal runaway, battery over-discharge, over-current, and more). Unlike wiring `ERROR`s (which block simulation at validation time), runtime faults describe what happens **during** a simulation when physics pushes a component out of spec.

## Diagnostic levels

| Level     | Meaning                        | Example                          | Simulation behaviour                 |
| --------- | ------------------------------ | -------------------------------- | ------------------------------------ |
| `INFO`    | Informational note             | Operating in a non-linear region | Continue                             |
| `WARNING` | Out of spec, degraded          | Battery SOC below 10%            | Continue, flag in results            |
| `ERROR`   | Invalid wiring or config       | Missing required port            | Block at `validate()`                |
| `FAULT`   | Catastrophic component failure | Motor windings burned out        | Degrade outputs; optionally stop sim |

`ERROR` is reserved for graph validation. `FAULT` is for mid-simulation catastrophic failures.

## Built-in fault-aware components

### DCMotor

Outputs `health` and `fault_code` in addition to `angular_velocity`, `current`, and `winding_temp`.

| Code  | Level   | Condition                                                                     |
| ----- | ------- | ----------------------------------------------------------------------------- |
| `1`   | WARNING | `MOTOR_THERMAL_HIGH`: winding temp approaching rated limit                    |
| `2`   | WARNING | `MOTOR_OVER_CURRENT`: current above `max_continuous_current`                  |
| `100` | FAULT   | `MOTOR_THERMAL_RUNAWAY`: winding temp exceeds `max_winding_temp`; motor stops |

Key parameters: `max_winding_temp` (default 130 °C), `max_continuous_current` (default 5 A), `warning_temp_fraction` (default 0.9).

### LiPoCell

| Code  | Level   | Condition                                                                                                  |
| ----- | ------- | ---------------------------------------------------------------------------------------------------------- |
| `1`   | WARNING | `CELL_LOW_SOC`: SOC below `min_soc_warning`                                                                |
| `2`   | WARNING | `CELL_HIGH_TEMP`: temperature approaching `max_temperature`                                                |
| `100` | FAULT   | `CELL_OVERDISCHARGE`: SOC = 0 or voltage at empty; discharge cut off                                       |
| `101` | FAULT   | `CELL_THERMAL_DAMAGE`: temperature exceeds `max_temperature`; internal resistance permanently increased 5× |

## Reading diagnostics from results

```python theme={null}
from hardwave.simulation import SimulationEngine, SimulationConfig, FaultMode
from hardwave.solvers import SimulationDomain
from hardwave.stdlib.components.motors import DCMotor

graph = SimulationGraph()
graph.add_component(DCMotor("motor", param_values={
    "max_winding_temp": 30.0,
    "winding_resistance": 0.1,
    "thermal_resistance": 100.0,
}))

result = SimulationEngine(graph).run(
    inputs={"motor": {"voltage": 24.0, "load_torque": 0.0}},
)

# Filter by severity
for d in result.get_warnings():
    print(d)

for d in result.get_faults():
    print(f"[{d.code}] {d.message} at t={d.timestamp}")

# Per-component filter
motor_diags = result.get_diagnostics_by_component("motor")

# Health output ports (wireable into control logic)
health = result.get_output("motor", "health")      # 0=OK, 1=warning, 2=fault
fault_code = result.get_output("motor", "fault_code")
```

Diagnostics are included in `result.to_dict()` with `step_index`, `timestamp`, `value`, and `threshold` when applicable.

## Fault modes

`SimulationConfig.fault_mode` controls how the engine responds to `FAULT`-level diagnostics:

```python theme={null}
from hardwave.simulation import SimulationConfig, FaultMode

# Default: keep simulating; faulted components return degraded outputs
config = SimulationConfig(fault_mode=FaultMode.CONTINUE)

# Abort on first fault (raises SimulationError)
config = SimulationConfig(fault_mode=FaultMode.STOP)
```

## Wiring health into control logic

Connect a component's `health` output to an MCU GPIO input to test firmware shutdown behaviour:

```python theme={null}
graph.connect("motor", "health", "mcu", "gpio_in_overtemp")
```

`health` is a `ControlSignal`: `0.0` = OK, `1.0` = warning, `2.0` = fault.

## Adding faults to custom components

Override two hooks on `Component`:

```python theme={null}
from hardwave.components.health import make_diagnostic
from hardwave.diagnostics import Diagnostic, DiagnosticLevel

class MyMotor(Component):
    FAULT_OVER_TEMP = 100

    def post_solve_diagnostics(self, inputs, outputs, *, t=None, step_index=None):
        diagnostics = []
        temp = outputs.get("winding_temp", 0.0)
        limit = self.get_param("max_temp")

        if temp >= limit:
            self.set_fault_code(self.FAULT_OVER_TEMP)
            diagnostics.append(make_diagnostic(
                DiagnosticLevel.FAULT,
                "MOTOR_THERMAL_RUNAWAY",
                f"Temperature {temp:.1f}°C exceeded {limit:.1f}°C",
                self.instance_id,
                port_name="winding_temp",
                step_index=step_index,
                timestamp=t,
                value=temp,
                threshold=limit,
            ))
        return diagnostics

    def apply_fault_outputs(self, outputs):
        degraded = dict(outputs)
        degraded["angular_velocity"] = 0.0
        degraded["current"] = 0.0
        return degraded
```

Add `health` and `fault_code` output ports to expose state on the graph:

```python theme={null}
output_port("health", "ControlSignal", "0=OK, 1=warning, 2=fault"),
output_port("fault_code", "ControlSignal", "Machine-readable fault code"),
```

The engine calls `post_solve_diagnostics()` after every solve step and populates `health` / `fault_code` automatically when those ports exist.

<Card title="Error Handling Reference" icon="triangle-exclamation" href="/reference/errors">
  Full exception hierarchy and validation diagnostic codes
</Card>
