# User-defined Devices

> Shows how to integrate a wallbox, meter, vehicle or heater without a built-in template by defining its readings and controls yourself via HTTP, MQTT or Modbus.

When creating a charger, heater, grid meter, battery, solar system, other consumer, vehicle, tariff, curtailment device or notification service, you can pick the **User-defined device** option from the device type list and describe your own logic based on the [plugin system](/en/reference/plugins).

That way you can integrate devices that aren’t covered by a built-in template, e.g. read the current power from an HTTP endpoint, or write a target current via Modbus.

## How to define a device

In the UI, every device type lists a **User-defined device** entry in the template selector. Picking it opens the editor for your own configuration. A device with `type: custom` is created by default. The type can be overridden with another value when needed (e.g. `type: heatpump`).

```yaml
power:
  source: mqtt
  topic: home/current/imsys/chn2/raw
```

Alternatively, write the full configuration directly in `evcc.yaml` with `name`, `type: custom` and the attribute block underneath:

```yaml
meters:
  - name: imsys
    type: custom
    power:
      source: mqtt
      topic: home/current/imsys/chn2/raw
```

All other examples on this page use the flat attribute-block form you enter in the UI.

For the list of available plugin sources (`http`, `mqtt`, `modbus`, …) and helpers (`calc`, `map`, `watchdog`, …) see the [plugins reference](/en/reference/plugins).

## Attributes and features

Every user-defined device is described by three kinds of fields:

* **Read attributes** — plugins evcc polls to obtain a value (e.g. `power`, `soc`, `status`).
* **Write attributes** — plugins evcc invokes to set a value or trigger an action (e.g. `enable`, `maxcurrent`, `wakeup`). The value to be written is available in the plugin.
* **Features** — `features` list toggling non-default behaviour. Available flags depend on the device type.

Generic shape:

```yaml
# read attributes
power:
  source: http
  uri: http://meter.local/power
soc:
  source: mqtt
  topic: battery/soc


# write attributes
enable:
  source: http
  uri: "http://charger/relay?turn={{if .enable}}on{{else}}off{{end}}"
maxcurrent:
  source: mqtt
  topic: charger/maxcurrent
  payload: ${maxcurrent:%d}


# features
features:
  - heating
  - integrateddevice
```

The available attribute names and feature flags differ per device type and are listed in the sections below.

## Meter

Power meters are configured in the [`meters`](/en/reference/configuration/meters) section. Meters defined under `meters:` can be referenced from `site`:

* `grid`: Grid meter
* `pv`: PV meter
* `battery`: Home battery meter
* `charge`: Meter for the charging power of the wallbox
* `aux`: Consumption meter for intelligent consumers
* `consumer`: Meter for a regular consumer, recorded for consumption statistics
* `ext`: Additional meter not used for regulation or shown in the UI, only logged and exported

`power` is the only required attribute. Not all meter roles support all attributes:

* `limitsoc` and `batterymode` are used exclusively for battery meters (referenced from `site.battery`).
* `curtail` and `curtailed` are used together for `pv` meters that support feed-in curtailment (§ 9 EEG).
* `currents`, `voltages` and `powers` are phase attributes that must be configured with exactly three plugin entries each (as a YAML array) and apply to grid meters (`grid`) and wallboxes (`charge`).

Return the correct data type from each plugin. To convert, use the [reading pipelines](/en/reference/plugins#reading).

### Read attributes

| Attribute      | Type                  | Required | Context       | Description                                                          |
| -------------- | --------------------- | -------- | ------------- | -------------------------------------------------------------------- |
| `power`        | `float`               | yes      | all           | Current power in W                                                   |
| `energy`       | `float`               | no       | all           | Meter reading in kWh                                                 |
| `returnenergy` | `float`               | no       | all           | Meter reading in reverse direction in kWh (see below)                |
| `maxpower`     | `int`                 | no       | `pv` (hybrid) | Maximum AC power in W                                                |
| `curtailed`    | `int`                 | no       | `pv`          | Current feed-in limit in % (0 to 100). Only together with `curtail`. |
| `soc`          | `int`                 | no       | `battery`     | State of charge in %                                                 |
| `capacity`     | `float`               | no       | `battery`     | Capacity in kWh                                                      |
| `powers`       | `[float,float,float]` | no       | all           | Phase powers in W. For sign detection of unsigned currents.          |
| `currents`     | `[float,float,float]` | no       | all           | Phase currents in A. For detecting active phases.                    |
| `voltages`     | `[float,float,float]` | no       | all           | Phase voltages in V. For connection detection (1p/3p).               |

### Signs and Directions

What a positive `power` value and the two energy directions mean depends on the meter role:

| Role                     | `power` positive                 | `energy`   | `returnenergy`     |
| ------------------------ | -------------------------------- | ---------- | ------------------ |
| `grid`                   | Grid import                      | Imported   | Exported (feed-in) |
| `pv`                     | Production                       | Produced   | Consumed (rare)    |
| `battery`                | Discharging (negative: charging) | Discharged | Charged            |
| `charge`                 | Charging                         | Charged    | Discharged (V2X)   |
| `aux`, `ext`, `consumer` | Consumption                      | Consumed   | Produced (rare)    |

`energy` and `returnenergy` are increasing meter readings in kWh, ideally lifetime totals like the import and export registers of a grid meter. The differences between readings are calculated automatically, so the plugin must return a meter reading and not consumption values per interval. A counter that resets periodically (e.g. daily) also works, as the reset is detected automatically. A reading of `0` is treated as not available.

Both attributes are optional. Without them, the energy history is derived from `power` over time. Real meter readings give more accurate long-term statistics.

### Write attributes

| Attribute     | Type  | Required | Context   | Description                                                                                                                                                       |
| ------------- | ----- | -------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `limitsoc`    | `int` | no       | `battery` | Set charging target for battery in %. The charging target is calculated from the configured `MinSoc`, `MaxSoc` and the current state of charge (attribute `soc`). |
| `curtail`     | `int` | no       | `pv`      | Set feed-in limit in % of the nominal production power (0 to 100, `100` = no limit). Only together with `curtailed`. See [Curtailment Device](#curtailer).        |
| `batterymode` | `int` | no       | `battery` | Set the battery control mode directly. The modes are listed below.                                                                                                |

#### Battery modes

| Value | Mode         | Behaviour                                                        |
| ----- | ------------ | ---------------------------------------------------------------- |
| `1`   | `normal`     | Normal operation: charge from surplus, discharge for consumption |
| `2`   | `hold`       | Prevent discharging, charging from surplus still allowed         |
| `3`   | `charge`     | Charge from grid, no discharging                                 |
| `4`   | `holdcharge` | Prevent charging, discharging for consumption still allowed      |
| `5`   | `discharge`  | Discharge to grid                                                |

Only the battery modes the device supports are used. If `batterymode` is a [`switch`](/en/reference/plugins#switch), the supported modes are taken from its `case` values. This also works when the switch is wrapped in a [`watchdog`](/en/reference/plugins#watchdog). Any other plugin needs `batterymodes` listing the supported modes, e.g. `batterymodes: ["normal", "hold", "charge"]`.

### Examples

Read the current grid power from an HTTP endpoint.

```yaml
meters:
  - name: grid
    type: custom
    power:
      source: http
      uri: http://zaehler.network.local:8080/api/data.json?from=now
      jq: .data.tuples[0][1]
```

Read power and charge level of a home battery and set its mode via HTTP. The `switch` covers the modes normal, hold and charge, so evcc offers exactly these three.

```yaml
meters:
  - name: battery
    type: custom
    capacity: 10 # kWh
    power:
      source: http
      uri: http://battery.local/api/power
    soc:
      source: http
      uri: http://battery.local/api/soc
    batterymode:
      source: switch
      switch:
        - case: 1 # normal
          set:
            source: http
            uri: http://battery.local/api/mode
            method: POST
            body: '{"mode": "auto"}'
        - case: 2 # hold
          set:
            source: http
            uri: http://battery.local/api/mode
            method: POST
            body: '{"mode": "hold"}'
        - case: 3 # charge
          set:
            source: http
            uri: http://battery.local/api/mode
            method: POST
            body: '{"mode": "charge"}'
```

## Charger

The default `type: custom` covers wallboxes with continuous current control. For other devices, specialised charger types are described under [Switchsocket](#charger-switchsocket) and [Heat pumps](#charger-heating).

### Read attributes

| Attribute      | Type                  | Required | Description                                                                                 |
| -------------- | --------------------- | -------- | ------------------------------------------------------------------------------------------- |
| `status`       | `string`              | yes      | Status (A..F)                                                                               |
| `enabled`      | `bool`                | yes      | Is charging enabled?                                                                        |
| `power`        | `float`               | no       | Charging power in W                                                                         |
| `energy`       | `float`               | no       | Meter reading in kWh                                                                        |
| `returnenergy` | `float`               | no       | Meter reading in reverse direction in kWh (discharged energy, V2X)                          |
| `identify`     | `string`              | no       | Current RFID identifier                                                                     |
| `soc`          | `int`                 | no       | State of charge in %                                                                        |
| `limitsoc`     | `int`                 | no       | Charge limit in %                                                                           |
| `temp`         | `float`               | no       | Current temperature in °C (heating, alias for `soc`)                                        |
| `limittemp`    | `int`                 | no       | Temperature limit in °C (heating, alias for `limitsoc`)                                     |
| `finishtime`   | `string`              | no       | Estimated charging finish time (RFC3339, Go duration, Unix timestamp, or remaining seconds) |
| `phases`       | `int`                 | no       | Number of physical phases (1..3)                                                            |
| `powers`       | `[float,float,float]` | no       | Phase powers in W. For sign detection of unsigned currents.                                 |
| `currents`     | `[float,float,float]` | no       | Phase currents in A. For detecting active phases.                                           |
| `voltages`     | `[float,float,float]` | no       | Phase voltages in V. For connection detection (1p/3p).                                      |

### Write attributes

| Attribute          | Type    | Required | Description                                       |
| ------------------ | ------- | -------- | ------------------------------------------------- |
| `enable`           | `bool`  | yes      | Enable / disable charging                         |
| `maxcurrent`       | `int`   | yes      | Set maximum charging current in A                 |
| `maxcurrentmillis` | `float` | no       | Set maximum charging current in A (with decimals) |
| `phases1p3p`       | `int`   | no       | Perform phase switching (requires `tos: true`)    |
| `wakeup`           | `bool`  | no       | Wake up vehicle                                   |

### Features

| Feature            | Description                                                                                                                                                                                                                                                                                                          |
| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `integrateddevice` | No vehicle selection. Device runs without a connected vehicle and without charging sessions (e.g. heat pump, heating rod, permanently installed consumer).                                                                                                                                                           |
| `heating`          | Treat device as heating: SOC and limits are displayed in °C instead of %.                                                                                                                                                                                                                                            |
| `continuous`       | Device keeps running in its own normal operation when “disabled”. The UI shows “Normal operation” instead of “Standby”. A request to increase power (e.g. on PV surplus or cheap grid power) is labelled “Boost”. Modes are labelled **Normal**, **Smart** and **Boost** instead of **Off**, **Smart** and **Fast**. |
| `switchdevice`     | Device can only be switched on/off (no continuous current control). Max current and the **Always charge** option are not shown. Min current and phases set the PV enable power. Without `continuous`, modes are labelled **Off**, **Smart** and **On**.                                                              |

Common feature combinations seen in built-in templates:

**Electric heater**:

```yaml
features:
  - integrateddevice
  - heating
```

**Socket**:

```yaml
features:
  - switchdevice
  - integrateddevice # optional, when the socket drives a fixed load
  - heating # optional, when it controls a heating device
```

**Heat pump**:

```yaml
features:
  - integrateddevice
  - heating
  - continuous
  - switchdevice # optional, when no current control is available (SG Ready)
```

### Examples

Query the charging status of a charger via Modbus.

```yaml
features:
  - integrateddevice
enabled:
  source: modbus
  id: 4711
  uri: modbus.local:502
  rtu: false
  register:
    address: 100
    type: holding
    decode: uint16
```

Switch a Tasmota socket via an MQTT message.

```yaml
enable:
  source: mqtt
  broker: mosquitto.local:883
  topic: cmd/unu-switch/Power
  payload: ON
```

### Switchsocket

**`type: switchsocket`**

For smart sockets and similar relay devices that can only be switched on/off without continuous current control. The charging status is derived from the current power (above `standbypower` means charging). Full setup details are under [Smart switches](/en/smartswitches).

| Attribute      | Type    | Required | Description                                                                              |
| -------------- | ------- | -------- | ---------------------------------------------------------------------------------------- |
| `enabled`      | `bool`  | yes      | Read socket state (on/off)                                                               |
| `power`        | `float` | yes      | Current power in W                                                                       |
| `energy`       | `float` | no       | Meter reading in kWh                                                                     |
| `soc`          | `float` | no       | State of charge in %                                                                     |
| `enable`       | `bool`  | yes      | Switch socket on/off                                                                     |
| `standbypower` | `float` | no       | Power threshold in W. Above it: charging; below: standby. Negative: static (no metering) |

### Heat pumps

Heat pumps and similar heating devices use dedicated charger types, each with its own attributes. The full setup is described under [Heat pumps, electric heaters](/en/heating).

* Heat pump

  **`type: heatpump`**

  For inverter-controlled heat pumps that accept a continuous power setpoint over Modbus, HTTP or similar. The target heating power is written directly via `setmaxpower`.

  | Attribute     | Type    | Required | Description                             |
  | ------------- | ------- | -------- | --------------------------------------- |
  | `power`       | `float` | no       | Current power in W                      |
  | `energy`      | `float` | no       | Meter reading in kWh                    |
  | `temp`        | `float` | no       | Current temperature in °C               |
  | `limittemp`   | `int`   | no       | Device-internal temperature limit in °C |
  | `setmaxpower` | `int`   | yes      | Set maximum heating power in W          |
  | `getmaxpower` | `float` | no       | Current maximum heating power in W      |

* SG-Ready

  **`type: sgready`**

  For heat pumps that expose the standard SG-Ready interface as a single mode value. Three modes are supported: `1` reduced, `2` normal, `3` boost.

  | Attribute     | Type    | Required | Description                                            |
  | ------------- | ------- | -------- | ------------------------------------------------------ |
  | `power`       | `float` | no       | Current power in W                                     |
  | `energy`      | `float` | no       | Meter reading in kWh                                   |
  | `temp`        | `float` | no       | Current temperature in °C                              |
  | `limittemp`   | `int`   | no       | Device-internal temperature limit in °C                |
  | `setmode`     | `int`   | yes      | Change SG-Ready mode (1: reduced, 2: normal, 3: boost) |
  | `getmode`     | `int`   | no       | Current SG-Ready mode (1, 2, 3)                        |
  | `setmaxpower` | `int`   | no       | Set maximum heating power in W                         |

* SG-Ready via relays

  **`type: sgready-relay`**

  For heat pumps whose SG-Ready input is wired as two dry relay contacts (boost + dim). Each contact is driven by a separate sub-charger referenced by type, not via plugins.

  | Attribute   | Type            | Required | Description                             |
  | ----------- | --------------- | -------- | --------------------------------------- |
  | `power`     | `float`         | no       | Current power in W                      |
  | `energy`    | `float`         | no       | Meter reading in kWh                    |
  | `temp`      | `float`         | no       | Current temperature in °C               |
  | `limittemp` | `int`           | no       | Device-internal temperature limit in °C |
  | `boost`     | `charger-typed` | yes      | Relay for the SG-Ready boost contact    |
  | `dim`       | `charger-typed` | no       | Relay for the SG-Ready dim contact      |

## Vehicle

Vehicle parameters can also be read via plugins.

### Read attributes

| Attribute       | Type     | Required | Description                                                                                 |
| --------------- | -------- | -------- | ------------------------------------------------------------------------------------------- |
| `soc`           | `int`    | yes      | State of charge in %                                                                        |
| `limitsoc`      | `int`    | no       | Charge limit in %                                                                           |
| `status`        | `string` | no       | Status (A..F)                                                                               |
| `range`         | `int`    | no       | Range in km                                                                                 |
| `odometer`      | `int`    | no       | Odometer reading in km                                                                      |
| `climater`      | `bool`   | no       | Climate control active?                                                                     |
| `getmaxcurrent` | `float`  | no       | Maximum charging current in A                                                               |
| `finishtime`    | `string` | no       | Estimated charging finish time (RFC3339, Go duration, Unix timestamp, or remaining seconds) |

### Write attributes

| Attribute      | Type   | Required | Description                       |
| -------------- | ------ | -------- | --------------------------------- |
| `wakeup`       | `bool` | no       | Wake up vehicle                   |
| `chargeenable` | `bool` | no       | Start/stop charging process       |
| `maxcurrent`   | `int`  | no       | Set maximum charging current in A |

### Configuration

| Attribute  | Type     | Required | Description                      |
| ---------- | -------- | -------- | -------------------------------- |
| `title`    | `string` | no       | Display name of the vehicle      |
| `capacity` | `float`  | no       | Battery capacity in kWh          |
| `icon`     | `string` | no       | Icon shown in the user interface |

### Features

| Feature         | Description                                                                                                                                                      |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `coarsecurrent` | Vehicle accepts charging current only in whole 1 A steps. The control logic is constrained to coarse 1 A steps even when the charger could regulate more finely. |
| `streaming`     | Vehicle pushes data instead of being polled (e.g. BMW Cardata). SOC updates outside active charging are treated as reliable.                                     |
| `welcomecharge` | Vehicle expects the wallbox to be active on connect to register the link as working. Otherwise it reports an error.                                              |

### Examples

Read the current range from MQTT messages.

```yaml
title: Green Mazda # display name (optional)
capacity: 50 # battery capacity in kWh (optional)
features:
  - coarsecurrent
range:
  source: mqtt
  topic: mazda2mqtt/c53/chargeInfo/drivingRangeKm
```

Wake a car via an HTTP ping before sending further queries.

```yaml
wakeup:
  source: http
  uri: http://teslalogger.local:5000/command/08154711/wake_up
```

[]()

`onIdentify` sets the charge mode automatically when the vehicle is detected.

```yaml
soc:
  source: mqtt
  topic: car/soc
onIdentify:
  mode: smart
```

Available modes: `off`, `smart`, `now`. The deprecated values `pv` and `minpv` are still accepted and activate `smart` with [Always charge](/en/features/modes#always-charge) off or on.

## Tariff and forecast

A user-defined tariff connects evcc to a custom value source via the plugin mechanism. The `tariff` attribute selects what the source represents and which unit the values use.

### Read attributes

Exactly one of `price` or `forecast` must be configured.

| Attribute  | Type     | Description                                                                                       |
| ---------- | -------- | ------------------------------------------------------------------------------------------------- |
| `price`    | `float`  | Current value. Float returned by the plugin.                                                      |
| `forecast` | `string` | Forecast as JSON string with a list of time periods and values (see schema below). Polled hourly. |

### Configuration

| Attribute      | Type       | Required | Description                                                                                                                                                                                     |
| -------------- | ---------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tariff`       | `string`   | no       | `price` (default), `co2`, `solar` or `temperature`. Sets the unit of the returned values: price in configured currency per kWh, CO₂ intensity in g/kWh, solar forecast in W, temperature in °C. |
| `charges`      | `float`    | no       | Fixed additional charge per kWh added to every value. Default `0`.                                                                                                                              |
| `chargesZones` | `list`     | no       | Time-based charges (e.g. grid fees) that override `charges` for specific periods. See [time-based grid fees](/en/reference/configuration/tariffs#charges-zones).                                |
| `tax`          | `float`    | no       | Percentage tax applied to the result, e.g. `0.2` for 20%. Default `0`.                                                                                                                          |
| `formula`      | `string`   | no       | Go expression for a custom calculation, with `price`, `charges` and `tax` in scope. See [examples](#formula-examples).                                                                          |
| `interval`     | `duration` | no       | Polling interval for `forecast`. Default `1h`.                                                                                                                                                  |
| `cache`        | `duration` | no       | Cache duration for `price`. Default `15m`.                                                                                                                                                      |

### Features

| Feature     | Description                                                                                    |
| ----------- | ---------------------------------------------------------------------------------------------- |
| `average`   | Smooths fine-grained price slots (e.g. 15-minute values) into hourly averages.                 |
| `cacheable` | Persists fetched values. Used as fallback after a restart or provider outage (up to 24 hours). |

### Examples

**Current price via HTTP**:

```yaml
price:
  source: http
  uri: https://example.com/api/price
```

**Forecast via HTTP**:

```yaml
forecast:
  source: http
  uri: https://api.allinpower.nl/troodon/api/p/spot_market/prices/?product_type=ELK
  jq: '[.timestamps, .prices] | transpose | map({ "start": (.[0] | strptime("%Y-%m-%dT%H:%M:%S.%f%z") | strftime("%Y-%m-%dT%H:%M:%SZ")), "end": (.[0] | strptime("%Y-%m-%dT%H:%M:%S.%f%z") | mktime + 3600 | strftime("%Y-%m-%dT%H:%M:%SZ")), "value": .[1] }) | tostring'
```

The plugin must return a JSON structure containing a list of time periods and prices. Date fields must be in the form `YYYY-MM-DDTHH:MM:SSZ` and the price in the correct currency unit (e.g. EUR). evcc works internally with 15-minute intervals; plugins may return hourly data, which is converted automatically.

```json
[
  {
    "start": "2025-01-01T00:00:00Z",
    "end": "2025-01-01T00:15:00Z",
    "value": 25.0
  },
  {
    "start": "2025-01-01T00:15:00Z",
    "end": "2025-01-01T00:30:00Z",
    "value": 26.5
  },
  {
    "start": "2025-01-01T00:30:00Z",
    "end": "2025-01-01T00:45:00Z",
    "value": 24.8
  },
  {
    "start": "2025-01-01T00:45:00Z",
    "end": "2025-01-01T01:00:00Z",
    "value": 27.2
  }
]
```

[]()

The `formula` field accepts a Go expression with `price`, `charges`, `tax` and the slot timestamp `ts` in scope. The [`math` library](https://pkg.go.dev/math) and [`time.Time`](https://pkg.go.dev/time#Time) methods on `ts` are available. The formula runs for the current price and each forecast slot.

**Price cap**:

```yaml
charges: 0.22
tax: 0.19
formula: math.Min(0.5, (price + charges) * (1 + tax))
```

Caps the result at 50 ct/kWh.

**No feed-in tariff on negative day-ahead prices** (German PV systems commissioned after February 25, 2025):

```yaml
formula: factor := 1.0; if price < 0 { factor = 0.0 }; factor * 0.07
```

Pays a fixed feed-in tariff of 7 ct/kWh, except when the day-ahead market price is negative.

## External Limit

A user-defined integration for the [External Limit](/en/external-limit) feature connects a control box or energy management system that isn’t covered by a built-in integration. In the UI, pick **User-defined integration** under **Configuration → External Limit**.

One of four types describes how the limit signal is received: `relay` (single switch contact), `fnn` (FNN control box with multiple switch contacts), `eebus` (EEBus protocol) or `custom` (dynamic limit values via plugins). Each signal is read via a [plugin](/en/reference/plugins) configuration (GPIO, MQTT, HTTP, Modbus). See also the [`hems` configuration reference](/en/reference/configuration/hems).

### Relay

The connection via a single switch contact is the simplest solution. The control box activates a contact which is evaluated by your evcc instance.

| Attribute     | Type       | Required | Description                                                                                      |
| ------------- | ---------- | -------- | ------------------------------------------------------------------------------------------------ |
| `maxPower`    | `int` (W)  | yes      | Total power limit applied while the signal is active.                                            |
| `limit`       | plugin     | yes      | Plugin configuration for reading the switch contact. `true`/`1` = limited, `false`/`0` = normal. |
| `passthrough` | plugin     | no       | Passes the limitation signal through to an external system.                                      |
| `interval`    | `duration` | no       | Polling interval for the switch contact. Default `10s`.                                          |

The power limit is communicated to you by the grid operator. For multiple controllable consumption devices (SteuVE), the simultaneity factor is taken into account. You can also calculate the limit yourself using the formula: **Total limit = Number of SteuVE × 4.2 kW × Simultaneity factor**. Details on the calculation can be found [here](https://www.inexogy.com/blog/14a-enwg/).

* Raspberry Pi GPIO

  When using a Raspberry Pi, the GPIO pin can be read directly:

  ```yaml
  type: relay
  maxPower: 8400 # Example for 2 SteuVE
  limit:
    source: gpio
    function: read
    pin: 17 # Read GPIO pin 17
    # Return value: false = not limited, true = limited
  ```

  For more details on the GPIO plugin, see the [plugin documentation](/en/reference/plugins#gpio).

* MQTT

  If the control box or gateway sends MQTT messages:

  ```yaml
  type: relay
  maxPower: 11340 # Example for 3 SteuVE with simultaneity factor 0.9
  limit:
    source: mqtt
    topic: hems/limit/status
    # Expected values: 0/false = normal, 1/true = limited
  ```

* HTTP API

  For control boxes with REST API:

  ```yaml
  type: relay
  maxPower: 13440 # Example for 4 SteuVE with simultaneity factor 0.8
  limit:
    source: http
    uri: http://steuerbox.local/api/limit
    jq: .limited # JSON path to boolean value
  ```

* Modbus

  If the control box or a gateway provides the power limit via Modbus:

  ```yaml
  type: relay
  maxPower: 4200 # Example for 1 SteuVE
  limit:
    source: modbus
    uri: 192.168.179.200:4703 # Example: relay on the 2nd S0 meter of a cFos Power Brain solar wallbox
    id: 3 # Modbus slave ID
    timeout: 5s
    register:
      type: holding
      decode: uint16
      address: 8056
    # Return value: 0 = not limited, 1 = limited
  ```

### FNN Control Box

Control boxes following the FNN standard signal dimming and curtailment via separate switch contacts. Consumption dimming (W4) and feed-in curtailment (W3, S2, S1) operate independently. At least one of the signals `w4` or `w3` must be configured.

| Attribute         | Type       | Required | Description                                                               |
| ----------------- | ---------- | -------- | ------------------------------------------------------------------------- |
| `maxDimPower`     | `int` (W)  | yes¹     | Consumption limit while the dim signal (W4) is active.                    |
| `maxCurtailPower` | `int` (W)  | yes²     | Installed solar power, base value for the curtailment steps.              |
| `w4`              | plugin     | no       | Reads the dim signal. Limits consumption to `maxDimPower`.                |
| `w3`              | plugin     | no       | Reads the curtailment signal. Limits feed-in to 0% of `maxCurtailPower`.  |
| `s2`              | plugin     | no       | Reads the curtailment signal. Limits feed-in to 30% of `maxCurtailPower`. |
| `s1`              | plugin     | no       | Reads the curtailment signal. Limits feed-in to 60% of `maxCurtailPower`. |
| `interval`        | `duration` | no       | Polling interval for the switch contacts. Default `10s`.                  |

¹ required when `w4` is configured, ² required when `w3` is configured.

```yaml
type: fnn
maxDimPower: 4200 # Consumption limit while dimmed (in watts)
maxCurtailPower: 10000 # Installed solar power, base for curtailment steps (in watts)
w4:
  source: gpio
  function: read
  pin: 17 # Read GPIO pin 17
  # Return value: false = normal, true = active
w3:
  source: gpio
  function: read
  pin: 27
s2:
  source: gpio
  function: read
  pin: 22
s1:
  source: gpio
  function: read
  pin: 23
```

### EEBus

The digital connection via the EEBus protocol. The control box communicates directly with your evcc instance and automatically transmits the power limit. Control box and evcc need to be [paired](/en/external-limit#eebus-pairing) once.

| Attribute                             | Type       | Required | Description                                                 |
| ------------------------------------- | ---------- | -------- | ----------------------------------------------------------- |
| `ski`                                 | `string`   | yes      | SKI (Subject Key Identifier) of the control box.            |
| `contractualConsumptionNominalMax`    | `int` (W)  | no       | Contractual maximum consumption power.                      |
| `failsafeConsumptionActivePowerLimit` | `int` (W)  | no       | Failsafe limit for consumption power.                       |
| `productionNominalMax`                | `int` (W)  | no       | Installed generator power (Wp).                             |
| `failsafeProductionActivePowerLimit`  | `int` (W)  | no       | Failsafe limit for feed-in power.                           |
| `failsafeDurationMinimum`             | `duration` | no       | Minimum failsafe duration, e.g. `2h`.                       |
| `passthrough`                         | plugin     | no       | Passes the limitation signal through to an external system. |

```yaml
type: eebus
ski: "1234-5678-90AB-CDEF" # SKI of the control box
```

### Custom

The generic integration for control systems that provide dynamic limit values instead of switch contacts. Consumption limit and feed-in curtailment are read via plugins and work independently of each other. At least one of `maxConsumptionPower` or `curtailedPercent` must be configured.

| Attribute              | Type       | Required | Description                                                                                           |
| ---------------------- | ---------- | -------- | ----------------------------------------------------------------------------------------------------- |
| `maxConsumptionPower`  | plugin     | no¹      | Reads the total power limit in watts. `0` = no limit.                                                 |
| `curtailedPercent`     | plugin     | no¹      | Reads the allowed feed-in as percentage of `productionNominalMax` (0 to 100). `100` = no curtailment. |
| `productionNominalMax` | `int` (W)  | no²      | Installed generator power (Wp).                                                                       |
| `interval`             | `duration` | no       | Polling interval for the plugins. Default `10s`.                                                      |

¹ at least one of the two is required, ² required when `curtailedPercent` is configured.

```yaml
type: custom
maxConsumptionPower:
  source: http
  uri: http://steuerbox.local/api/limit
  jq: .maxPower # Power limit in watts, 0 = no limit
curtailedPercent:
  source: mqtt
  topic: hems/curtail/percent # Allowed feed-in in percent, 100 = no curtailment
productionNominalMax: 10000 # Installed generator power (Wp)
```

## Curtailment Device

A user-defined curtailment device limits the feed-in of the solar installation on request of the grid operator (§ 9 EEG) when the inverter can’t be curtailed through its [meter](#meter) configuration. In the UI, pick **User-defined device** under **Configuration → Grid → Add curtailment device**. See also the [`curtailers` configuration reference](/en/reference/configuration/curtailers) and [External Limit](/en/external-limit#curtailment-devices).

The device receives the allowed feed-in as a percentage of the installed generator power and has to apply it on the inverter. `100` means no limit.

### Attributes

| Attribute   | Type   | Required | Description                                                                                                                               |
| ----------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `curtail`   | plugin | yes      | Sets the feed-in limit in % of the nominal production power (0 to 100). Must support write access.                                        |
| `curtailed` | plugin | yes      | Reads the current feed-in limit in %. If the device can’t report it, use the [`error`](/en/reference/plugins#error) plugin (see example). |

### Examples

* HTTP

  ```yaml
  curtail:
    source: http
    uri: http://inverter.local/api/limit?percent={{ .curtail }}
    method: POST
  curtailed:
    source: http
    uri: http://inverter.local/api/limit
    jq: .percent
  ```

* Modbus

  The inverter can’t report its limit, so `curtailed` returns `ErrNotAvailable` and the limit is written whenever it changes.

  ```yaml
  curtail:
    source: modbus
    uri: 192.168.0.10:502
    id: 1
    register:
      type: writesingle
      address: 40016
      encoding: uint16
  curtailed:
    source: error
    error: ErrNotAvailable
  ```

## Notification Service

A user-defined notification service processes [notification](/en/notifications) messages via any [plugin](/en/reference/plugins) with write access, e.g. a shell script, HTTP request, or MQTT message. In the UI, pick **User-defined service** under **Configuration → Notifications → Services**.

The message is provided to the plugin in the `${send}` parameter (or as a template parameter `{{.send}}`).

### Configuration

| Attribute  | Type     | Required | Description                                                 |
| ---------- | -------- | -------- | ----------------------------------------------------------- |
| `send`     | plugin   | yes      | Plugin invoked for each message. Must support write access. |
| `encoding` | `string` | no       | Format of the value provided in `${send}`. See below.       |

The possible `encoding` values are:

| Encoding | Content of `${send}`                                                                                    |
| -------- | ------------------------------------------------------------------------------------------------------- |
| `json`   | JSON object `{ "msg": msg, "title": title }`. The `title` field is only added if defined for the event. |
| `csv`    | `title` and `msg` as a comma-separated list                                                             |
| `tsv`    | Like `csv`, but tab-separated                                                                           |
| `title`  | Only the title                                                                                          |
| (none)   | Only the message (`msg`)                                                                                |

### Example

```yaml
messaging:
  events:
    connect:
      title: "${vehicleTitle} connected"
      msg: "${vehicleTitle} was connected (charging mode: ${mode})."
  services:
    - type: custom
      encoding: json
      send:
        # plugin type
        source: script
        # plugin-specific configuration;
        # {{.send}} contains the JSON message
        cmd: /usr/local/bin/evcc_message "{{.send}}"
```

In this example, a shell script (`cmd`) is invoked with the argument `{"title": "...", "msg": "..."}`.