[fronius] Add Thing actions to prevent battery from charging & Refactorings (#18846)

* [fronius] Refactor battery control actions to reduce code redundancy

Signed-off-by: Florian Hotze <dev@florianhotze.com>
This commit is contained in:
Florian Hotze
2025-06-27 12:01:14 +02:00
committed by GitHub
parent 9669f0445b
commit fc770b5736
5 changed files with 187 additions and 129 deletions
@@ -185,6 +185,8 @@ Once the actions instance has been retrieved, you can invoke the following metho
- `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.
- `preventBatteryCharging()`: Prevent the battery from charging (removes all battery control schedules first and applies all the time).
- `addPreventBatteryChargingSchedule(LocalTime from, LocalTime until)`: Add a schedule to prevent the battery from charging in the specified time range.
- `setBackupReservedBatteryCapacity(int percent)`: Set the reserved battery capacity for backup power.
- `setBackupReservedBatteryCapacity(PercentType percent)`: Set the reserved battery capacity for backup power.
@@ -203,6 +205,7 @@ 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'));
froniusInverterActions.addPreventBatteryChargingSchedule(time.toZDT('09:00'), time.toZDT('12:00'));
froniusInverterActions.setBackupReservedBatteryCapacity(50);
```
@@ -19,6 +19,9 @@ import javax.measure.quantity.Power;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.fronius.internal.api.FroniusBatteryControl;
import org.openhab.binding.fronius.internal.api.FroniusCommunicationException;
import org.openhab.binding.fronius.internal.api.FroniusUnauthorizedException;
import org.openhab.binding.fronius.internal.handler.FroniusSymoInverterHandler;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.ActionOutput;
@@ -30,6 +33,8 @@ 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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of the {@link ThingActions} interface used for controlling battery charging and discharging for
@@ -41,6 +46,7 @@ import org.osgi.service.component.annotations.ServiceScope;
@ThingActionsScope(name = "fronius")
@NonNullByDefault
public class FroniusSymoInverterActions implements ThingActions {
private final Logger logger = LoggerFactory.getLogger(FroniusSymoInverterActions.class);
private @Nullable FroniusSymoInverterHandler handler;
public static boolean resetBatteryControl(ThingActions actions) {
@@ -98,6 +104,34 @@ public class FroniusSymoInverterActions implements ThingActions {
return addForcedBatteryChargingSchedule(actions, from.toLocalTime(), until.toLocalTime(), power);
}
public static boolean preventBatteryCharging(ThingActions actions) {
if (actions instanceof FroniusSymoInverterActions froniusSymoInverterActions) {
return froniusSymoInverterActions.preventBatteryCharging();
} else {
throw new IllegalArgumentException(
"The 'actions' argument is not an instance of FroniusSymoInverterActions");
}
}
public static boolean addPreventBatteryChargingSchedule(ThingActions actions, LocalTime from, LocalTime until) {
if (actions instanceof FroniusSymoInverterActions froniusSymoInverterActions) {
return froniusSymoInverterActions.addPreventBatteryChargingSchedule(from, until);
} else {
throw new IllegalArgumentException(
"The 'actions' argument is not an instance of FroniusSymoInverterActions");
}
}
public static boolean addPreventBatteryChargingSchedule(ThingActions actions, ZonedDateTime from,
ZonedDateTime until) {
if (actions instanceof FroniusSymoInverterActions froniusSymoInverterActions) {
return froniusSymoInverterActions.addPreventBatteryChargingSchedule(from, until);
} else {
throw new IllegalArgumentException(
"The 'actions' argument is not an instance of FroniusSymoInverterActions");
}
}
public static boolean setBackupReservedBatteryCapacity(ThingActions actions, int percent) {
if (actions instanceof FroniusSymoInverterActions froniusSymoInverterActions) {
return froniusSymoInverterActions.setBackupReservedBatteryCapacity(percent);
@@ -128,18 +162,24 @@ public class FroniusSymoInverterActions implements ThingActions {
@RuleAction(label = "@text/actions.reset-battery-control.label", description = "@text/actions.reset-battery-control.description")
public @ActionOutput(type = "boolean", label = "Success") boolean resetBatteryControl() {
FroniusSymoInverterHandler handler = this.handler;
if (handler != null) {
return handler.resetBatteryControl();
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
return executeBatteryControlAction(() -> {
batteryControl.reset();
return true;
});
}
return false;
}
@RuleAction(label = "@text/actions.hold-battery-charge.label", description = "@text/actions.hold-battery-charge.description")
public @ActionOutput(type = "boolean", label = "Success") boolean holdBatteryCharge() {
FroniusSymoInverterHandler handler = this.handler;
if (handler != null) {
return handler.holdBatteryCharge();
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
return executeBatteryControlAction(() -> {
batteryControl.holdBatteryCharge();
return true;
});
}
return false;
}
@@ -148,9 +188,12 @@ public class FroniusSymoInverterActions implements ThingActions {
public @ActionOutput(type = "boolean", label = "Success") boolean addHoldBatteryChargeSchedule(
@ActionInput(name = "from", label = "@text/actions.from.label", description = "@text/actions.from.description", required = true) LocalTime from,
@ActionInput(name = "until", label = "@text/actions.until.label", description = "@text/actions.until.description", required = true) LocalTime until) {
FroniusSymoInverterHandler handler = this.handler;
if (handler != null) {
return handler.addHoldBatteryChargeSchedule(from, until);
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
return executeBatteryControlAction(() -> {
batteryControl.addHoldBatteryChargeSchedule(from, until);
return true;
});
}
return false;
}
@@ -162,9 +205,12 @@ public class FroniusSymoInverterActions implements ThingActions {
@RuleAction(label = "@text/actions.force-battery-charging.label", description = "@text/actions.force-battery-charging.description")
public @ActionOutput(type = "boolean", label = "Success") boolean forceBatteryCharging(
@ActionInput(name = "power", label = "@text/actions.power.label", description = "@text/actions.power.label", type = "QuantityType<Power>", required = true) QuantityType<Power> power) {
FroniusSymoInverterHandler handler = this.handler;
if (handler != null) {
return handler.forceBatteryCharging(power);
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
return executeBatteryControlAction(() -> {
batteryControl.forceBatteryCharging(power);
return true;
});
}
return false;
}
@@ -174,9 +220,12 @@ public class FroniusSymoInverterActions implements ThingActions {
@ActionInput(name = "from", label = "@text/actions.from.label", description = "@text/actions.from.description", required = true) LocalTime from,
@ActionInput(name = "until", label = "@text/actions.until.label", description = "@text/actions.until.description", required = true) LocalTime until,
@ActionInput(name = "power", label = "@text/actions.power.label", description = "@text/actions.power.label", type = "QuantityType<Power>", required = true) QuantityType<Power> power) {
FroniusSymoInverterHandler handler = this.handler;
if (handler != null) {
return handler.addForcedBatteryChargingSchedule(from, until, power);
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
return executeBatteryControlAction(() -> {
batteryControl.addForcedBatteryChargingSchedule(from, until, power);
return true;
});
}
return false;
}
@@ -186,12 +235,50 @@ public class FroniusSymoInverterActions implements ThingActions {
return addForcedBatteryChargingSchedule(from.toLocalTime(), until.toLocalTime(), power);
}
@RuleAction(label = "@text/actions.prevent-battery-charging.label", description = "@text/actions.prevent-battery-charging.description")
public @ActionOutput(type = "boolean", label = "Success") boolean preventBatteryCharging() {
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
return executeBatteryControlAction(() -> {
batteryControl.preventBatteryCharging();
return true;
});
}
return false;
}
@RuleAction(label = "@text/actions.add-prevent-battery-charging-schedule.label", description = "@text/actions.add-prevent-battery-charging-schedule.description")
public @ActionOutput(type = "boolean", label = "Success") boolean addPreventBatteryChargingSchedule(
@ActionInput(name = "from", label = "@text/actions.from.label", description = "@text/actions.from.description", required = true) LocalTime from,
@ActionInput(name = "until", label = "@text/actions.until.label", description = "@text/actions.until.description", required = true) LocalTime until) {
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
return executeBatteryControlAction(() -> {
batteryControl.addPreventBatteryChargingSchedule(from, until);
return true;
});
}
return false;
}
public boolean addPreventBatteryChargingSchedule(ZonedDateTime from, ZonedDateTime until) {
return addPreventBatteryChargingSchedule(from.toLocalTime(), until.toLocalTime());
}
@RuleAction(label = "@text/actions.backup-reserved-battery-capacity.label", description = "@text/actions.backup-reserved-battery-capacity.description")
public @ActionOutput(type = "boolean", label = "Success") boolean setBackupReservedBatteryCapacity(
@ActionInput(name = "percent", label = "@text/actions.soc.label", description = "@text/actions.soc.description", required = true) int percent) {
FroniusSymoInverterHandler handler = this.handler;
if (handler != null) {
return handler.setBackupReservedBatteryCapacity(percent);
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
return executeBatteryControlAction(() -> {
try {
batteryControl.setBackupReservedCapacity(percent);
} catch (IllegalArgumentException e) {
logger.warn("Failed to set backup reserved battery capacity: {}", e.getMessage());
return false;
}
return true;
});
}
return false;
}
@@ -199,4 +286,34 @@ public class FroniusSymoInverterActions implements ThingActions {
public boolean setBackupReservedBatteryCapacity(PercentType percent) {
return setBackupReservedBatteryCapacity(percent.intValue());
}
private @Nullable FroniusBatteryControl getBatteryControl() {
FroniusSymoInverterHandler handler = this.handler;
if (handler != null) {
return handler.getBatteryControl();
}
return null;
}
@FunctionalInterface
public interface ExceptionalSupplier<T> {
T get() throws FroniusCommunicationException, FroniusUnauthorizedException;
}
/**
* Executes a battery control action provided through a {@link ExceptionalSupplier} and handles common exceptions.
*
* @param supplier the action to execute
* @return the result of the action
*/
private boolean executeBatteryControlAction(ExceptionalSupplier<Boolean> supplier) {
try {
return supplier.get();
} catch (FroniusCommunicationException e) {
logger.warn("Failed to execute battery control action", e);
} catch (FroniusUnauthorizedException e) {
logger.warn("Failed to execute battery control action: Invalid username or password");
}
return false;
}
}
@@ -53,7 +53,6 @@ public class FroniusBatteryControl {
private static final String BATTERIES_ENDPOINT = "/config/batteries";
private static final String BACKUP_RESERVED_CAPACITY_PARAMETER = "HYB_BACKUP_RESERVED";
private static final Logger LOGGER = LoggerFactory.getLogger(FroniusBatteryControl.class);
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
private static final WeekdaysRecord ALL_WEEKDAYS_RECORD = new WeekdaysRecord(true, true, true, true, true, true,
@@ -61,6 +60,7 @@ public class FroniusBatteryControl {
private static final LocalTime BEGIN_OF_DAY = LocalTime.of(0, 0);
private static final LocalTime END_OF_DAY = LocalTime.of(23, 59);
private final Logger logger = LoggerFactory.getLogger(FroniusBatteryControl.class);
private final Gson gson = new Gson();
private final HttpClient httpClient;
private final URI baseUri;
@@ -83,7 +83,7 @@ public class FroniusBatteryControl {
*
* @return the time of use settings
* @throws FroniusCommunicationException if an error occurs during communication with the inverter
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
* @throws FroniusUnauthorizedException when the login fails due to invalid credentials
*/
private TimeOfUseRecords getTimeOfUse() throws FroniusCommunicationException, FroniusUnauthorizedException {
// Login and get the auth header for the next request
@@ -94,7 +94,7 @@ public class FroniusBatteryControl {
// Get the time of use settings
String response = FroniusHttpUtil.executeUrl(HttpMethod.GET, timeOfUseUri.toString(), headers, null, null,
API_TIMEOUT);
LOGGER.trace("Time of Use settings read successfully");
logger.trace("Time of Use settings read successfully");
// Parse the response body
TimeOfUseRecords records;
@@ -114,7 +114,7 @@ public class FroniusBatteryControl {
*
* @param records the time of use settings
* @throws FroniusCommunicationException if an error occurs during communication with the inverter
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
* @throws FroniusUnauthorizedException when the login fails due to invalid credentials
*/
private void setTimeOfUse(TimeOfUseRecords records)
throws FroniusCommunicationException, FroniusUnauthorizedException {
@@ -130,10 +130,10 @@ public class FroniusBatteryControl {
new ByteArrayInputStream(json.getBytes()), "application/json", API_TIMEOUT);
PostConfigResponse response = gson.fromJson(responseString, PostConfigResponse.class);
if (!response.writeSuccess().contains("timeofuse")) {
LOGGER.debug("{}", responseString);
logger.debug("{}", responseString);
throw new FroniusCommunicationException("Failed to write configuration to inverter");
}
LOGGER.trace("Time of Use settings set successfully");
logger.trace("Time of Use settings set successfully");
}
/**
@@ -141,7 +141,7 @@ public class FroniusBatteryControl {
* inverter.
*
* @throws FroniusCommunicationException when an error occurs during communication with the inverter
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
* @throws FroniusUnauthorizedException when the login fails due to invalid credentials
*/
public void reset() throws FroniusCommunicationException, FroniusUnauthorizedException {
setTimeOfUse(new TimeOfUseRecords(new TimeOfUseRecord[0]));
@@ -151,7 +151,7 @@ public class FroniusBatteryControl {
* Holds the battery charge right now, i.e. prevents the battery from discharging.
*
* @throws FroniusCommunicationException when an error occurs during communication with the inverter
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
* @throws FroniusUnauthorizedException when the login fails due to invalid credentials
*/
public void holdBatteryCharge() throws FroniusCommunicationException, FroniusUnauthorizedException {
reset();
@@ -165,7 +165,7 @@ public class FroniusBatteryControl {
* @param from start time of the hold charge period
* @param until end time of the hold charge period
* @throws FroniusCommunicationException when an error occurs during communication with the inverter
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
* @throws FroniusUnauthorizedException when the login fails due to invalid credentials
*/
public void addHoldBatteryChargeSchedule(LocalTime from, LocalTime until)
throws FroniusCommunicationException, FroniusUnauthorizedException {
@@ -184,7 +184,7 @@ public class FroniusBatteryControl {
*
* @param power the power to charge the battery with
* @throws FroniusCommunicationException when an error occurs during communication with the inverter
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
* @throws FroniusUnauthorizedException when the login fails due to invalid credentials
*/
public void forceBatteryCharging(QuantityType<Power> power)
throws FroniusCommunicationException, FroniusUnauthorizedException {
@@ -199,7 +199,7 @@ public class FroniusBatteryControl {
* @param until end time of the forced charge period
* @param power the power to charge the battery with
* @throws FroniusCommunicationException when an error occurs during communication with the inverter
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
* @throws FroniusUnauthorizedException when the login fails due to invalid credentials
*/
public void addForcedBatteryChargingSchedule(LocalTime from, LocalTime until, QuantityType<Power> power)
throws FroniusCommunicationException, FroniusUnauthorizedException {
@@ -214,6 +214,37 @@ public class FroniusBatteryControl {
setTimeOfUse(new TimeOfUseRecords(timeOfUse));
}
/**
* Prevents the battery from charging right now.
*
* @throws FroniusCommunicationException when an error occurs during communication with the inverter
* @throws FroniusUnauthorizedException when the login fails due to invalid credentials
*/
public void preventBatteryCharging() throws FroniusCommunicationException, FroniusUnauthorizedException {
reset();
addPreventBatteryChargingSchedule(BEGIN_OF_DAY, END_OF_DAY);
}
/**
* Prevents the battery from charging during a specific time period.
*
* @param from start time of the prevented charging period
* @param until end time of the prevented charging period
* @throws FroniusCommunicationException when an error occurs during communication with the inverter
* @throws FroniusUnauthorizedException when the login fails due to invalid credentials
*/
public void addPreventBatteryChargingSchedule(LocalTime from, LocalTime until)
throws FroniusCommunicationException, FroniusUnauthorizedException {
TimeOfUseRecords currentTimeOfUse = getTimeOfUse();
TimeOfUseRecord[] timeOfUse = new TimeOfUseRecord[currentTimeOfUse.records().length + 1];
System.arraycopy(currentTimeOfUse.records(), 0, timeOfUse, 0, currentTimeOfUse.records().length);
TimeOfUseRecord preventCharging = new TimeOfUseRecord(true, 0, ScheduleType.CHARGE_MAX,
new TimeTableRecord(from.format(TIME_FORMATTER), until.format(TIME_FORMATTER)), ALL_WEEKDAYS_RECORD);
timeOfUse[timeOfUse.length - 1] = preventCharging;
setTimeOfUse(new TimeOfUseRecords(timeOfUse));
}
/**
* Sets the reserved battery capacity for backup power.
*
@@ -240,9 +271,9 @@ public class FroniusBatteryControl {
new ByteArrayInputStream(json.getBytes()), "application/json", API_TIMEOUT);
PostConfigResponse response = gson.fromJson(responseString, PostConfigResponse.class);
if (!response.writeSuccess().contains(BACKUP_RESERVED_CAPACITY_PARAMETER)) {
LOGGER.debug("{}", responseString);
logger.debug("{}", responseString);
throw new FroniusCommunicationException("Failed to write configuration to inverter");
}
LOGGER.trace("Backup Reserved Capacity setting set successfully");
logger.trace("Backup Reserved Capacity setting set successfully");
}
}
@@ -13,14 +13,12 @@
package org.openhab.binding.fronius.internal.handler;
import java.net.URI;
import java.time.LocalTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.measure.Unit;
import javax.measure.quantity.Power;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
@@ -30,7 +28,6 @@ import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
import org.openhab.binding.fronius.internal.action.FroniusSymoInverterActions;
import org.openhab.binding.fronius.internal.api.FroniusBatteryControl;
import org.openhab.binding.fronius.internal.api.FroniusCommunicationException;
import org.openhab.binding.fronius.internal.api.FroniusUnauthorizedException;
import org.openhab.binding.fronius.internal.api.dto.ValueUnit;
import org.openhab.binding.fronius.internal.api.dto.inverter.InverterDeviceStatus;
import org.openhab.binding.fronius.internal.api.dto.inverter.InverterRealtimeBody;
@@ -115,107 +112,13 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
initializeBatteryControl(bridgeConfig.hostname, bridgeConfig.username, bridgeConfig.password);
}
private @Nullable FroniusBatteryControl getBatteryControl() {
public @Nullable FroniusBatteryControl getBatteryControl() {
if (batteryControl == null) {
logger.warn("Battery control is not available. Check the bridge configuration.");
}
return batteryControl;
}
public boolean resetBatteryControl() {
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
try {
batteryControl.reset();
return true;
} catch (FroniusCommunicationException e) {
logger.warn("Failed to reset battery control", e);
} catch (FroniusUnauthorizedException e) {
logger.warn("Failed to reset battery control: Invalid username or password");
}
}
return false;
}
public boolean holdBatteryCharge() {
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
try {
batteryControl.holdBatteryCharge();
return true;
} catch (FroniusCommunicationException e) {
logger.warn("Failed to set battery control to hold battery charge", e);
} catch (FroniusUnauthorizedException e) {
logger.warn("Failed to set battery control to hold battery charge: Invalid username or password");
}
}
return false;
}
public boolean addHoldBatteryChargeSchedule(LocalTime from, LocalTime until) {
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
try {
batteryControl.addHoldBatteryChargeSchedule(from, until);
return true;
} catch (FroniusCommunicationException e) {
logger.warn("Failed to add hold battery charge schedule to battery control", e);
} catch (FroniusUnauthorizedException e) {
logger.warn(
"Failed to add hold battery charge schedule to battery control: Invalid username or password");
}
}
return false;
}
public boolean forceBatteryCharging(QuantityType<Power> power) {
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
try {
batteryControl.forceBatteryCharging(power);
return true;
} catch (FroniusCommunicationException e) {
logger.warn("Failed to set battery control to force battery charge", e);
} catch (FroniusUnauthorizedException e) {
logger.warn("Failed to set battery control to force battery charge: Invalid username or password");
}
}
return false;
}
public boolean addForcedBatteryChargingSchedule(LocalTime from, LocalTime until, QuantityType<Power> power) {
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
try {
batteryControl.addForcedBatteryChargingSchedule(from, until, power);
return true;
} catch (FroniusCommunicationException e) {
logger.warn("Failed to add forced battery charge schedule to battery control", e);
} catch (FroniusUnauthorizedException e) {
logger.warn(
"Failed to add forced battery charge schedule to battery control: Invalid username or password");
}
}
return false;
}
public boolean setBackupReservedBatteryCapacity(int percent) {
FroniusBatteryControl batteryControl = getBatteryControl();
if (batteryControl != null) {
try {
batteryControl.setBackupReservedCapacity(percent);
return true;
} catch (IllegalArgumentException e) {
logger.warn("Failed to set backup reserved battery capacity: {}", e.getMessage());
} catch (FroniusCommunicationException e) {
logger.warn("Failed to set backup reserved battery capacity", e);
} catch (FroniusUnauthorizedException e) {
logger.warn("Failed to set backup reserved battery capacity: Invalid username or password");
}
}
return false;
}
/**
* Update the channel from the last data retrieved
*
@@ -128,6 +128,10 @@ actions.force-battery-charging.label = Force Battery Charging
actions.force-battery-charging.description = Force the battery to charge with the specified power
actions.add-forced-battery-charging-schedule.label = Add Forced Battery Charging Schedule
actions.add-forced-battery-charging-schedule.description = Add a schedule to force the battery to charge with the specified power in the specified time range
actions.prevent-battery-charging.label = Prevent Battery Charging
actions.prevent-battery-charging.description = Prevent the battery from charging
actions.add-prevent-battery-charging-schedule.label = Add Prevent Battery Charging Schedule
actions.add-prevent-battery-charging-schedule.description = Add a schedule to prevent the battery from charging in the specified time range
actions.backup-reserved-battery-capacity.label = Backup Power Reserved Battery Capacity
actions.backup-reserved-battery-capacity.description = Set the reserved battery capacity for backup power supply
actions.from.label = Begin Timestamp