> ## 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.

# Defining Components

> Create custom Hardwave components with ports, parameters, and solvers

Components are Python classes with typed ports, configurable parameters, and a `solve()` method. Hardwave provides a decorator for quick authoring and a manual registration path for full control.

## Using the @component decorator

```python theme={null}
from hardwave import component, input_port, output_port, Component, Parameter

@component(
    name="Thermistor",
    display_name="NTC Thermistor",
    version="1.0.0",
    description="NTC thermistor: resistance decreases with temperature",
    docs="Uses the Steinhart-Hart equation approximation.",
    category="Sensors",
    tags=["sensor", "temperature", "thermistor"],
)
class Thermistor(Component):
    ports = [
        input_port("temperature", "Temperature", "Ambient temperature (°C)"),
        output_port("resistance", "Resistance", "Thermistor resistance at given temperature"),
    ]

    parameters = [
        Parameter("r_nominal",  "Nominal Resistance", "Ω",   float, 10_000.0),
        Parameter("t_nominal",  "Nominal Temperature", "°C", float, 25.0),
        Parameter("beta",       "Beta Coefficient",    "K",  float, 3950.0),
    ]

    def solve(self, inputs: dict) -> dict:
        import math
        T_c = inputs["temperature"]
        T_k = T_c + 273.15
        T0 = self.get_param("t_nominal") + 273.15
        R0 = self.get_param("r_nominal")
        B  = self.get_param("beta")
        R  = R0 * math.exp(B * (1 / T_k - 1 / T0))
        return {"resistance": R}
```

The decorator automatically attaches `ComponentMeta` and registers the class with `ComponentRegistry`.

## Manual definition

```python theme={null}
from hardwave.components import Component, ComponentMeta, ComponentRegistry, Parameter
from hardwave.ports import input_port, output_port

class MyGasSensor(Component):
    meta = ComponentMeta(
        name="MyGasSensor",
        display_name="Custom Gas Sensor",
        version="1.0.0",
        description="Reads CO2 concentration",
        category="Sensors/Gas",
    )
    ports = [
        input_port("analog_in", "AnalogSignal", "Sensor output voltage (0-5V)"),
        output_port("co2_ppm", "AnalogSignal",  "CO2 concentration in PPM"),
    ]
    parameters = [
        Parameter("sensitivity", "Sensitivity", "V/PPM", float, 0.002),
    ]

    def solve(self, inputs: dict) -> dict:
        V = inputs["analog_in"]
        ppm = V / self.get_param("sensitivity")
        return {"co2_ppm": ppm}

ComponentRegistry.instance().register(MyGasSensor)
```

## Parameters

`Parameter` defines static configuration on a component instance:

```python theme={null}
Parameter(
    name="t_min",
    display_name="Lower Temperature Threshold",
    unit="°C",
    python_type=float,
    default=20.0,
    description="Temperature at or below which the vent stays shut",
)
```

Access parameters at solve time with `self.get_param("t_min")`. Override defaults when adding instances to a graph:

```python theme={null}
graph.add_component(ThermostatController("ctrl", param_values={"t_min": 22.0, "t_max": 38.0}))
```

## Optional ports

Mark input ports as optional with a default:

```python theme={null}
input_port("load_torque", "Torque", "Load on the servo shaft",
           required=False, default=0.0)
```

## Aggregating input ports

When several upstream components should drive the same input, use `aggregating_input_port()`. The engine gathers all connected values and produces one value for `solve()`.

```python theme={null}
from hardwave.ports import aggregating_input_port, PortAggregation

def net_current(values, component, port):
    return sum(values)

ports = [
    # Built-in sum
    aggregating_input_port(
        "current_in", "Current",
        aggregation=PortAggregation.SUM,
        description="Total current from all branches",
    ),
    # Custom callable: fully replaces built-in aggregation
    aggregating_input_port("current_in", "Current", aggregate=net_current),
]
```

Built-in modes include `SUM`, `MEAN`, `MIN`, `MAX`, `PRODUCT`, `FIRST`, and `LAST`. Optional `min_connections` and `max_connections` enforce wiring limits at validation or connect time.

<Card title="Aggregating Input Ports" icon="merge" href="/guides/aggregating-ports">
  Full guide with graph wiring, disconnect APIs, and serialization notes
</Card>

## Testing in isolation

Call `solve()` directly before wiring a component into a graph:

```python theme={null}
controller = ThermostatController("ctrl")
result = controller.solve({"temperature": 27.5})
print(result)  # {'vent_state': 0.5, 'pwm_out': {...}}
```

## Injecting a custom solver

Replace the default `solve()` logic without changing ports or wiring:

```python theme={null}
from hardwave.solvers import FormulaSolver

def my_formula(inputs, component):
    V = inputs["voltage"]
    R = component.get_param("resistance")
    return {"current": V / R, "power": V**2 / R}

r = Resistor("r1", param_values={"resistance": 470.0})
r.set_solver(FormulaSolver(my_formula))
```

See [Solvers](/simulation/solvers) for ODE, lookup table, and ML solver options.

## Runtime fault diagnostics

Components can emit `WARNING` and `FAULT` diagnostics when operating limits are exceeded during simulation. Implement two optional hooks:

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

def post_solve_diagnostics(self, inputs, outputs, *, t=None, step_index=None):
  # Return a list of Diagnostic objects; call self.set_fault_code() for FAULTs
  return []

def apply_fault_outputs(self, outputs):
  # Return modified outputs when self.is_faulted is True
  return outputs
```

Expose `health` and `fault_code` output ports so other components (e.g. an MCU) can react to faults:

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

<Card title="Component Faults and Health" icon="triangle-exclamation" href="/guides/component-faults">
  Built-in fault rules, FaultMode, and result inspection
</Card>
