Skip to content

Commit

Permalink
[fronius] Add battery control Thing actions (#17170)
Browse files Browse the repository at this point in the history
* [fronius] Add DTOs for /config/timeofuse HTTP endpoint

Signed-off-by: Florian Hotze <florianh_dev@icloud.com>
  • Loading branch information
florian-h05 authored Jul 30, 2024
1 parent e6fa6a4 commit bfce4b2
Show file tree
Hide file tree
Showing 15 changed files with 1,024 additions and 21 deletions.
92 changes: 77 additions & 15 deletions bundles/org.openhab.binding.fronius/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@

This binding uses the [Fronius Solar API V1](https://www.fronius.com/en/solar-energy/installers-partners/technical-data/all-products/system-monitoring/open-interfaces/fronius-solar-api-json-) to obtain data from Fronius devices.

It supports Fronius inverters and Fronius Smart Meter.
Supports:
It supports Fronius inverters, smart meters and Ohmpilot devices connected to a Fronius Datamanager 1.0 / 2.0, Fronius Datalogger or with integrated Solar API V1 support.

Inverters with integrated Solar API V1 support include:

- Fronius Galvo
- Fronius Primo
- Fronius Symo
- Fronius Symo Gen24
- Fronius Smart Meter 63A
- Fronius Smart Meter TS 65A-3
- Fronius Ohmpilot
- Fronius Symo Gen24 Plus

## Supported Things

| Thing Type | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bridge` | The Bridge |
| `powerinverter` | Fronius Galvo, Symo and other Fronius inverters in combination with the Fronius Datamanager 1.0 / 2.0 or Fronius Datalogger. You can add multiple inverters that depend on the same datalogger with different device ids. (Default 1) |
| `meter` | Fronius Smart Meter. You can add multiple smart meters with different device ids. (The default id = 0) |
| `ohmpilot` | Fronius Ohmpilot. (The default id = 0) |
| Thing Type | Description |
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `bridge` | The Bridge |
| `powerinverter` | Fronius Galvo, Symo and other Fronius inverters: You can add multiple inverters that depend on the same datalogger with different device ids. (default id = 1) |
| `meter` | Fronius Smart Meter: You can add multiple smart meters with different device ids. (default id = 0) |
| `ohmpilot` | Fronius Ohmpilot ( default id = 0) |

## Discovery

Expand All @@ -32,10 +33,12 @@ The binding has no configuration options, all configuration is done at `bridge`,

### Bridge Thing Configuration

| Parameter | Description |
| ----------------- | ----------------------------------------------------- |
| `hostname` | The hostname or IP address of your Fronius Datalogger |
| `refreshInterval` | Refresh interval in seconds |
| Parameter | Description | Required |
|-------------------|--------------------------------------------------------------------------------|----------|
| `hostname` | The hostname or IP address of your Fronius Datamanager, Datalogger or inverter | Yes |
| `username` | The username to authenticate with the inverter settings for battery control | No |
| `password` | The password to authenticate with the inverter settings for battery control | No |
| `refreshInterval` | Refresh interval in seconds | No |

### Powerinverter Thing Configuration

Expand Down Expand Up @@ -138,6 +141,65 @@ The binding has no configuration options, all configuration is done at `bridge`,
| `modelId` | The model name of the ohmpilot |
| `serialNumber` | The serial number of the ohmpilot |

## Actions

:::tip Warning
Battery control uses the battery management's time-dependent battery control settings of the inverter settings and therefore overrides user-specified time of use settings.
Please note that user-specified time of use plans cannot be used together with battery control, as battery control will override the user-specified time of use settings.
:::

The `powerinverter` Thing provides actions to control the battery charging and discharging behaviour of hybrid inverters, such as Symo Gen24 Plus, if username and password are provided in the bridge configuration.

You can retrieve the actions as follows:

:::: tabs

::: tab DSL

```java
val froniusInverterActions = getActions("fronius", "fronius:powerinverter:mybridge:myinverter")
```
:::

::: tab JS

```javascript
var froniusInverterActions = actions.thingActions('fronius', 'fronius:powerinverter:mybridge:myinverter');
```

:::

::::

Where the first parameter must always be `fronius` and the second must be the full Thing UID of the inverter.

### Available Actions

Once the actions instance has been retrieved, you can invoke the following methods:

- `resetBatteryControl()`: Remove all battery control schedules from the inverter.
- `holdBatteryCharge()`: Prevent the battery from discharging (removes all battery control schedules first and applies all the time).
- `addHoldBatteryChargeSchedule(LocalTime from, LocalTime until)`: Add a schedule to prevent the battery from discharging in the specified time range.
- `addHoldBatteryChargeSchedule(ZonedDateTime from, ZonedDateTime until)`: Add a schedule to prevent the battery from discharging in the specified time range.
- `forceBatteryCharging(QuantityType<Power> power)`: Force the battery to charge with the specified power (removes all battery control schedules first and applies all the time).
- `addForcedBatteryChargingSchedule(LocalTime from, LocalTime until, QuantityType<Power> power)`: Add a schedule to force the battery to charge with the specified power in the specified time range.
- `addForcedBatteryChargingSchedule(ZonedDateTime from, ZonedDateTime until, QuantityType<Power> power)`: Add a schedule to force the battery to charge with the specified power in the specified time range.

### Examples

```javascript
var froniusInverterActions = actions.thingActions('fronius', 'fronius:powerinverter:mybridge:myinverter');

froniusInverterActions.resetBatteryControl();
froniusInverterActions.holdBatteryCharge();
froniusInverterActions.forceBatteryCharging(Quantity('5 kW'));

froniusInverterActions.resetBatteryControl();
froniusInverterActions.addHoldBatteryChargeSchedule(time.toZDT('18:00'), time.toZDT('22:00'));
froniusInverterActions.addForcedBatteryChargingSchedule(time.toZDT('22:00'), time.toZDT('23:59'), Quantity('5 kW'));
froniusInverterActions.addForcedBatteryChargingSchedule(time.toZDT('00:00'), time.toZDT('06:00'), Quantity('5 kW'));
```

## Full Example

demo.things:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@
*/
public class FroniusBridgeConfiguration {
public String hostname;
public String username;
public String password;
public Integer refreshInterval;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@
import org.openhab.binding.fronius.internal.handler.FroniusMeterHandler;
import org.openhab.binding.fronius.internal.handler.FroniusOhmpilotHandler;
import org.openhab.binding.fronius.internal.handler.FroniusSymoInverterHandler;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

/**
* The {@link FroniusHandlerFactory} is responsible for creating things and thing
Expand All @@ -54,6 +57,13 @@ public class FroniusHandlerFactory extends BaseThingHandlerFactory {
}
};

private final HttpClientFactory httpClientFactory;

@Activate
public FroniusHandlerFactory(@Reference HttpClientFactory httpClientFactory) {
this.httpClientFactory = httpClientFactory;
}

@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
Expand All @@ -64,7 +74,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();

if (thingTypeUID.equals(THING_TYPE_INVERTER)) {
return new FroniusSymoInverterHandler(thing);
return new FroniusSymoInverterHandler(thing, httpClientFactory.getCommonHttpClient());
} else if (thingTypeUID.equals(THING_TYPE_BRIDGE)) {
return new FroniusBridgeHandler((Bridge) thing);
} else if (thingTypeUID.equals(THING_TYPE_METER)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/**
* Copyright (c) 2010-2024 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.fronius.internal.action;

import java.time.LocalTime;
import java.time.ZonedDateTime;

import javax.measure.quantity.Power;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.fronius.internal.handler.FroniusSymoInverterHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.RuleAction;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.thing.binding.ThingActions;
import org.openhab.core.thing.binding.ThingActionsScope;
import org.openhab.core.thing.binding.ThingHandler;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;

/**
* Implementation of the {@link ThingActions} interface used for controlling battery charging and discharging for
* Fronius hybrid inverters.
*
* @author Florian Hotze - Initial contribution
*/
@Component(scope = ServiceScope.PROTOTYPE, service = FroniusSymoInverterActions.class)
@ThingActionsScope(name = "fronius")
@NonNullByDefault
public class FroniusSymoInverterActions implements ThingActions {
private @Nullable FroniusSymoInverterHandler handler;

public static void resetBatteryControl(ThingActions actions) {
if (actions instanceof FroniusSymoInverterActions froniusSymoInverterActions) {
froniusSymoInverterActions.resetBatteryControl();
} else {
throw new IllegalArgumentException(
"The 'actions' argument is not an instance of FroniusSymoInverterActions");
}
}

public static void holdBatteryCharge(ThingActions actions) {
if (actions instanceof FroniusSymoInverterActions froniusSymoInverterActions) {
froniusSymoInverterActions.holdBatteryCharge();
} else {
throw new IllegalArgumentException(
"The 'actions' argument is not an instance of FroniusSymoInverterActions");
}
}

public static void addHoldBatteryChargeSchedule(ThingActions actions, LocalTime from, LocalTime until) {
if (actions instanceof FroniusSymoInverterActions froniusSymoInverterActions) {
froniusSymoInverterActions.addHoldBatteryChargeSchedule(from, until);
} else {
throw new IllegalArgumentException(
"The 'actions' argument is not an instance of FroniusSymoInverterActions");
}
}

public static void addHoldBatteryChargeSchedule(ThingActions actions, ZonedDateTime from, ZonedDateTime until) {
addHoldBatteryChargeSchedule(actions, from.toLocalTime(), until.toLocalTime());
}

public static void forceBatteryCharging(ThingActions actions, QuantityType<Power> power) {
if (actions instanceof FroniusSymoInverterActions froniusSymoInverterActions) {
froniusSymoInverterActions.forceBatteryCharging(power);
} else {
throw new IllegalArgumentException(
"The 'actions' argument is not an instance of FroniusSymoInverterActions");
}
}

public static void addForcedBatteryChargingSchedule(ThingActions actions, LocalTime from, LocalTime until,
QuantityType<Power> power) {
if (actions instanceof FroniusSymoInverterActions froniusSymoInverterActions) {
froniusSymoInverterActions.addForcedBatteryChargingSchedule(from, until, power);
} else {
throw new IllegalArgumentException(
"The 'actions' argument is not an instance of FroniusSymoInverterActions");
}
}

public static void addForcedBatteryChargingSchedule(ThingActions actions, ZonedDateTime from, ZonedDateTime until,
QuantityType<Power> power) {
addForcedBatteryChargingSchedule(actions, from.toLocalTime(), until.toLocalTime(), power);
}

@Override
public void setThingHandler(@Nullable ThingHandler handler) {
this.handler = (FroniusSymoInverterHandler) handler;
}

@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
}

@RuleAction(label = "@text/actions.reset-battery-control.label", description = "@text/actions.reset-battery-control.description")
public void resetBatteryControl() {
FroniusSymoInverterHandler handler = this.handler;
if (handler != null) {
handler.resetBatteryControl();
}
}

@RuleAction(label = "@text/actions.hold-battery-charge.label", description = "@text/actions.hold-battery-charge.description")
public void holdBatteryCharge() {
FroniusSymoInverterHandler handler = this.handler;
if (handler != null) {
handler.holdBatteryCharge();
}
}

@RuleAction(label = "@text/actions.add-hold-battery-charge-schedule.label", description = "@text/actions.add-hold-battery-charge-schedule.description")
public void addHoldBatteryChargeSchedule(
@ActionInput(name = "from", label = "@text/actions.from.label", description = "@text/actions.from.description") LocalTime from,
@ActionInput(name = "until", label = "@text/actions.until.label", description = "@text/actions.until.description") LocalTime until) {
FroniusSymoInverterHandler handler = this.handler;
if (handler != null) {
handler.addHoldBatteryChargeSchedule(from, until);
}
}

public void addHoldBatteryChargeSchedule(ZonedDateTime from, ZonedDateTime until) {
addHoldBatteryChargeSchedule(from.toLocalTime(), until.toLocalTime());
}

@RuleAction(label = "@text/actions.force-battery-charging.label", description = "@text/actions.force-battery-charging.description")
public void forceBatteryCharging(
@ActionInput(name = "power", label = "@text/actions.power.label", description = "@text/actions.power.label") QuantityType<Power> power) {
FroniusSymoInverterHandler handler = this.handler;
if (handler != null) {
handler.forceBatteryCharging(power);
}
}

@RuleAction(label = "@text/actions.add-forced-battery-charging-schedule.label", description = "@text/actions.add-forced-battery-charging-schedule.description")
public void addForcedBatteryChargingSchedule(
@ActionInput(name = "from", label = "@text/actions.from.label", description = "@text/actions.from.description") LocalTime from,
@ActionInput(name = "until", label = "@text/actions.until.label", description = "@text/actions.until.description") LocalTime until,
@ActionInput(name = "power", label = "@text/actions.power.label", description = "@text/actions.power.label") QuantityType<Power> power) {
FroniusSymoInverterHandler handler = this.handler;
if (handler != null) {
handler.addForcedBatteryChargingSchedule(from, until, power);
}
}

public void addForcedBatteryChargingSchedule(ZonedDateTime from, ZonedDateTime until, QuantityType<Power> power) {
addForcedBatteryChargingSchedule(from.toLocalTime(), until.toLocalTime(), power);
}
}
Loading

0 comments on commit bfce4b2

Please sign in to comment.