From 642794fc0d2e481a3aed0eb962172a74d2f5651e Mon Sep 17 00:00:00 2001 From: Paul Smedley Date: Fri, 28 Jun 2024 21:03:54 +0930 Subject: [PATCH] [teslapowerwall] Initial contribution (#16876) * Latest fixes Signed-off-by: Paul Smedley --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + .../org.openhab.binding.teslapowerwall/NOTICE | 13 ++ .../README.md | 100 ++++++++++ .../pom.xml | 17 ++ .../src/main/feature/feature.xml | 9 + ...TeslaPowerwallAuthenticationException.java | 38 ++++ .../TeslaPowerwallBindingConstants.java | 58 ++++++ .../TeslaPowerwallCommunicationException.java | 38 ++++ .../internal/TeslaPowerwallConfiguration.java | 28 +++ .../internal/TeslaPowerwallHandler.java | 173 +++++++++++++++++ .../TeslaPowerwallHandlerFactory.java | 63 ++++++ ...TeslaPowerwallTlsTrustManagerProvider.java | 40 ++++ .../internal/TeslaPowerwallWebTargets.java | 160 ++++++++++++++++ .../internal/api/BatterySOE.java | 40 ++++ .../internal/api/GridStatus.java | 43 +++++ .../internal/api/MeterAggregates.java | 71 +++++++ .../internal/api/Operations.java | 43 +++++ .../internal/api/SystemStatus.java | 40 ++++ .../src/main/resources/OH-INF/addon/addon.xml | 11 ++ .../OH-INF/i18n/teslapowerwall.properties | 181 ++++++++++++++++++ .../resources/OH-INF/thing/thing-types.xml | 170 ++++++++++++++++ bundles/pom.xml | 1 + 23 files changed, 1343 insertions(+) create mode 100644 bundles/org.openhab.binding.teslapowerwall/NOTICE create mode 100644 bundles/org.openhab.binding.teslapowerwall/README.md create mode 100644 bundles/org.openhab.binding.teslapowerwall/pom.xml create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallAuthenticationException.java create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallBindingConstants.java create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallCommunicationException.java create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallConfiguration.java create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallHandler.java create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallHandlerFactory.java create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallTlsTrustManagerProvider.java create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallWebTargets.java create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/BatterySOE.java create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/GridStatus.java create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/MeterAggregates.java create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/Operations.java create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/SystemStatus.java create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/resources/OH-INF/addon/addon.xml create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/resources/OH-INF/i18n/teslapowerwall.properties create mode 100644 bundles/org.openhab.binding.teslapowerwall/src/main/resources/OH-INF/thing/thing-types.xml diff --git a/CODEOWNERS b/CODEOWNERS index 292a42062bf..a2d901c9626 100755 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -368,6 +368,7 @@ /bundles/org.openhab.binding.teleinfo/ @Nokyyz @olivierkeke /bundles/org.openhab.binding.tellstick/ @openhab/add-ons-maintainers /bundles/org.openhab.binding.tesla/ @kgoderis +/bundles/org.openhab.binding.teslapowerwall/ @psmedley /bundles/org.openhab.binding.tibber/ @kjoglum /bundles/org.openhab.binding.tivo/ @mlobstein /bundles/org.openhab.binding.touchwand/ @roieg diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 1bcffbae283..631c9f7a82f 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -1831,6 +1831,11 @@ org.openhab.binding.tesla ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.teslapowerwall + ${project.version} + org.openhab.addons.bundles org.openhab.binding.tibber diff --git a/bundles/org.openhab.binding.teslapowerwall/NOTICE b/bundles/org.openhab.binding.teslapowerwall/NOTICE new file mode 100644 index 00000000000..38d625e3492 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.teslapowerwall/README.md b/bundles/org.openhab.binding.teslapowerwall/README.md new file mode 100644 index 00000000000..fa0c4719d17 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/README.md @@ -0,0 +1,100 @@ +# TeslaPowerwall Binding + +This binding enables the capture of key data from a Tesla Powerwall 2 into openHAB. + +## Supported Things + +- `powerwall` Tesla Powerwall 2 + +## Discovery + +The binding does not support auto discovery. + +## Thing Configuration + +As a minimum, the hostname is needed: + +* hostname - The hostname of the Tesla Powerwall 2. Defaults to powerwall to avoid SSL certificate issues +* email - the email of the local account on the Powerwall that the installer provided +* password - the password of the local account on the Powerwall that the installer provided +* refresh - The frequency with which to refresh information from the Tesla Powerwall2 specified in seconds. Defaults to 10 seconds. + +## Channels + +| channel id | type | description | +|---------------------------|----------------------|--------------------------------------------------------------| +| grid-status | String | Current status of the Power Grid | +| battery-soe | Number:Dimensionless | Current battery state of charge | +| mode | String | Current operating mode | +| reserve | Number:Dimensionless | Current battery reserve % | +| grid-inst-power | Number:Power | Instantaneous Grid Power Supply | +| battery-inst-power | Number:Power | Instantaneous Battery Power Supply | +| home-inst-power | Number:Power | Instantaneous Home Power Supply | +| solar-inst-power | Number:Power | Instantaneous Solar Power Supply | +| grid-energy-exported | Number:Energy | Total Grid Energy Exported | +| battery-energy-exported | Number:Energy | Total Battery Energy Exported | +| home-energy-exported | Number:Energy | Total Home Energy Exported | +| solar-energy-exported | Number:Energy | Total Solar Energy Exported | +| grid-energy-imported | Number:Energy | Total Grid Energy Imported | +| battery-energy-imported | Number:Energy | Total Battery Energy Imported | +| home-energy-imported | Number:Energy | Total Home Energy Imported | +| solar-energy-imported | Number:Energy | Total Solar Energy Imported | +| degradation | Number:Dimensionless | Current battery degradation % (Based on single battery) | +| full-pack-energy | Number:Energy | Reported battery capacity at full | + +## Full Example + +### `teslapowerwall.things`: + +```java +teslapowerwall:tesla-powerwall:TeslaPowerwall [ hostname="192.168.0.5" ] +``` + +### `teslapowerwall.items`: + +```java +String TeslaPowerwall_grid-status { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:grid-status" } +Switch TeslaPowerwall_grid-services { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:grid-services" } +Number:Dimensionless TeslaPowerwall_battery-soe { channel="tesla-powerwall:teslapowerwall:TeslaPowerwall:battery-soe", unit="%" } +String TeslaPowerwall_mode { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:mode" } +Number:Dimensionless TeslaPowerwall_reserve { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:reserve", unit="%" } +Number:Power TeslaPowerwall_grid-inst-power { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:grid-inst-power" } +Number:Power TeslaPowerwall_battery-inst-power { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:battery-inst-power" } +Number:Power TeslaPowerwall_home-inst-power { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:home-inst-power" } +Number:Power TeslaPowerwall_solar-inst-power { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:solar-inst-power" } +Number:Energy TeslaPowerwall_grid-energy-exported { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:grid-energy-exported" } +Number:Energy TeslaPowerwall_grid-energy-imported { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:grid-energy-imported" } +Number:Energy TeslaPowerwall_battery-energy-exported { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:battery-energy-exported" } +Number:Energy TeslaPowerwall_battery-energy-imported { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:battery-energy-imported" } +Number:Energy TeslaPowerwall_home-energy-exported { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:home-energy-exported" } +Number:Energy TeslaPowerwall_home-energy-imported { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:home-energy-imported" } +Number:Energy TeslaPowerwall_solar-energy-exported { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:solar-energy-exported" } +Number:Energy TeslaPowerwall_solar-energy-imported { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:solar-energy-imported" } +Number:Dimensionless TeslaPowerwall_degradation { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:degradation", unit="%" } +Number:Energy TeslaPowerwall_full-pack-energy { channel="teslapowerwall:tesla-powerwall:TeslaPowerwall:full-pack-energy" } +``` + +### `teslapowerwall.sitemap`: + +```perl +Text item=TeslaPowerwall_grid-status label="Grid Status [%s]" +Text item=TeslaPowerwall_grid-services label="Grid Services Status [%s]" +Text item=TeslaPowerwall_battery-soe label="Battery Charge" +Text item=TeslaPowerwall_mode label="Battery Mode" +Text item=TeslaPowerwall_reserve label="Battery Reserve" +Text item=TeslaPowerwall_grid-inst-power label="Grid Power [%.1f W]" +Text item=TeslaPowerwall_battery-inst-power label="Battery Power [%.1f W]" +Text item=TeslaPowerwall_home-inst-power label="Home Power [%.1f W]" +Text item=TeslaPowerwall_solar-inst-power label="Solar Power [%.1f W]" +Text item=TeslaPowerwall_grid-energy-exported label="Grid Energy Exported [%.1f kWh]" +Text item=TeslaPowerwall_grid-energy-imported label="Grid Energy Imported [%.1f kWh]" +Text item=TeslaPowerwall_battery-energy-exported label="Battery Energy Exported [%.1f kWh]" +Text item=TeslaPowerwall_battery-energyi-mported label="Battery Energy Imported [%.1f kWh]" +Text item=TeslaPowerwall_home-energy-exported label="Home Energy Exported [%.1f kWh]" +Text item=TeslaPowerwall_home-energy-imported label="Home Energy Imported [%.1f kWh]" +Text item=TeslaPowerwall_solar-energy-exported label="Solar Energy Exported [%.1f kWh]" +Text item=TeslaPowerwall_solar-energy-imported label="Solar Energy Imported [%.1f kWh]" +Text item=TeslaPowerwall_full-pack-energy label="Full Pack Energy" +Text item=TeslaPowerwall_degradation label="Degradation level" +``` + diff --git a/bundles/org.openhab.binding.teslapowerwall/pom.xml b/bundles/org.openhab.binding.teslapowerwall/pom.xml new file mode 100644 index 00000000000..5746fde8928 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 4.2.0-SNAPSHOT + + + org.openhab.binding.teslapowerwall + + openHAB Add-ons :: Bundles :: Tesla Powerwall Binding + + diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/feature/feature.xml b/bundles/org.openhab.binding.teslapowerwall/src/main/feature/feature.xml new file mode 100644 index 00000000000..99d637a5e7f --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/feature/feature.xml @@ -0,0 +1,9 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + mvn:org.openhab.addons.bundles/org.openhab.binding.teslapowerwall/${project.version} + + diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallAuthenticationException.java b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallAuthenticationException.java new file mode 100644 index 00000000000..c658c6cc85a --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallAuthenticationException.java @@ -0,0 +1,38 @@ +/** + * 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.teslapowerwall.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Exception for when an unexpected response is received from the TeslaPowerwall controller. + * + * @author Paul Smedley - Initial contribution + * + */ +@NonNullByDefault +public class TeslaPowerwallAuthenticationException extends Exception { + private static final long serialVersionUID = 529232811860854017L; + + public TeslaPowerwallAuthenticationException(String message) { + super(message); + } + + public TeslaPowerwallAuthenticationException(Throwable ex) { + super(ex); + } + + public TeslaPowerwallAuthenticationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallBindingConstants.java b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallBindingConstants.java new file mode 100644 index 00000000000..98be26a9df0 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallBindingConstants.java @@ -0,0 +1,58 @@ +/** + * 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.teslapowerwall.internal; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingTypeUID; + +/** + * The {@link TeslaPowerwallBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Paul Smedley - Initial contribution + */ +@NonNullByDefault +public class TeslaPowerwallBindingConstants { + + private static final String BINDING_ID = "teslapowerwall"; + + // List of all Thing Type UIDs + public static final ThingTypeUID TESLAPOWERWALL_THING = new ThingTypeUID(BINDING_ID, "tesla-powerwall"); + + // List of all Channel ids + public static final String CHANNEL_TESLAPOWERWALL_GRID_STATUS = "grid-status"; + public static final String CHANNEL_TESLAPOWERWALL_GRID_SERVICES = "grid-services"; + public static final String CHANNEL_TESLAPOWERWALL_BATTERY_SOE = "battery-soe"; + public static final String CHANNEL_TESLAPOWERWALL_MODE = "mode"; + public static final String CHANNEL_TESLAPOWERWALL_RESERVE = "reserve"; + public static final String CHANNEL_TESLAPOWERWALL_GRID_INST_POWER = "grid-inst-power"; + public static final String CHANNEL_TESLAPOWERWALL_GRID_ENERGY_EXPORTED = "grid-energy-exported"; + public static final String CHANNEL_TESLAPOWERWALL_GRID_ENERGY_IMPORTED = "grid-energy-imported"; + public static final String CHANNEL_TESLAPOWERWALL_BATTERY_INST_POWER = "battery-inst-power"; + public static final String CHANNEL_TESLAPOWERWALL_BATTERY_ENERGY_IMPORTED = "battery-energy-imported"; + public static final String CHANNEL_TESLAPOWERWALL_BATTERY_ENERGY_EXPORTED = "battery-energy-exported"; + public static final String CHANNEL_TESLAPOWERWALL_HOME_INST_POWER = "home-inst-power"; + public static final String CHANNEL_TESLAPOWERWALL_HOME_ENERGY_EXPORTED = "home-energy-exported"; + public static final String CHANNEL_TESLAPOWERWALL_HOME_ENERGY_IMPORTED = "home-energy-imported"; + public static final String CHANNEL_TESLAPOWERWALL_SOLAR_INST_POWER = "solar-inst-power"; + public static final String CHANNEL_TESLAPOWERWALL_SOLAR_ENERGY_EXPORTED = "solar-energy-exported"; + public static final String CHANNEL_TESLAPOWERWALL_SOLAR_ENERGY_IMPORTED = "solar-energy-imported"; + public static final String CHANNEL_TESLAPOWERWALL_FULL_PACK_ENERGY = "full-pack-energy"; + public static final String CHANNEL_TESLAPOWERWALL_DEGRADATION = "degradation"; + + public static final int TESLA_POWERWALL_CAPACITY = 13500; + + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(TESLAPOWERWALL_THING); +} diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallCommunicationException.java b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallCommunicationException.java new file mode 100644 index 00000000000..d4f1f1a3a27 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallCommunicationException.java @@ -0,0 +1,38 @@ +/** + * 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.teslapowerwall.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Exception for when an unexpected response is received from the TeslaPowerwall controller. + * + * @author Paul Smedley - Initial contribution + * + */ +@NonNullByDefault +public class TeslaPowerwallCommunicationException extends Exception { + private static final long serialVersionUID = 529232811860854017L; + + public TeslaPowerwallCommunicationException(String message) { + super(message); + } + + public TeslaPowerwallCommunicationException(Throwable ex) { + super(ex); + } + + public TeslaPowerwallCommunicationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallConfiguration.java b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallConfiguration.java new file mode 100644 index 00000000000..d7dbf8a4e46 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallConfiguration.java @@ -0,0 +1,28 @@ +/** + * 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.teslapowerwall.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The TeslaPowerwallConfiguration class contains fields mapping thing configuration parameters. + * + * @author Paul Smedley - Initial contribution + */ +@NonNullByDefault +public class TeslaPowerwallConfiguration { + public String hostname = ""; + public long refresh = 10; + public String email = ""; + public String password = ""; +} diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallHandler.java b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallHandler.java new file mode 100644 index 00000000000..9a2562100d9 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallHandler.java @@ -0,0 +1,173 @@ +/** + * 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.teslapowerwall.internal; + +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.teslapowerwall.internal.api.BatterySOE; +import org.openhab.binding.teslapowerwall.internal.api.GridStatus; +import org.openhab.binding.teslapowerwall.internal.api.MeterAggregates; +import org.openhab.binding.teslapowerwall.internal.api.Operations; +import org.openhab.binding.teslapowerwall.internal.api.SystemStatus; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.MetricPrefix; +import org.openhab.core.library.unit.Units; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link TeslaPowerwallHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Paul Smedley - Initial contribution + */ +@NonNullByDefault +public class TeslaPowerwallHandler extends BaseThingHandler { + + private final Logger logger = LoggerFactory.getLogger(TeslaPowerwallHandler.class); + + private @NonNullByDefault({}) TeslaPowerwallConfiguration config; + private @NonNullByDefault({}) TeslaPowerwallWebTargets webTargets; + private @Nullable ScheduledFuture pollFuture; + + public TeslaPowerwallHandler(Thing thing, HttpClient httpClient) { + super(thing); + config = getConfigAs(TeslaPowerwallConfiguration.class); + webTargets = new TeslaPowerwallWebTargets(config.hostname, httpClient); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + logger.warn("This binding is read only"); + } + + @Override + public void initialize() { + config = getConfigAs(TeslaPowerwallConfiguration.class); + logger.debug("config.hostname = {}, refresh = {}", config.hostname, config.refresh); + if (config.hostname.isBlank() || config.email.isBlank() || config.password.isBlank()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.conf-error.missing-config-key"); + return; + } else { + updateStatus(ThingStatus.UNKNOWN); + schedulePoll(); + } + } + + @Override + public void dispose() { + super.dispose(); + stopPoll(); + } + + private void schedulePoll() { + ScheduledFuture pollFuture = this.pollFuture; + if (pollFuture != null) { + pollFuture.cancel(false); + } + logger.debug("Scheduling poll for every {} s", config.refresh); + this.pollFuture = scheduler.scheduleWithFixedDelay(this::pollStatus, 0, config.refresh, TimeUnit.SECONDS); + } + + private void stopPoll() { + final Future future = pollFuture; + if (future != null) { + future.cancel(true); + pollFuture = null; + } + } + + private void pollStatus() { + Operations operations = null; + BatterySOE batterySOE = null; + GridStatus gridStatus = null; + SystemStatus systemStatus = null; + MeterAggregates meterAggregates = null; + try { + operations = webTargets.getOperations(config.email, config.password); + batterySOE = webTargets.getBatterySOE(config.email, config.password); + gridStatus = webTargets.getGridStatus(config.email, config.password); + systemStatus = webTargets.getSystemStatus(config.email, config.password); + meterAggregates = webTargets.getMeterAggregates(config.email, config.password); + updateStatus(ThingStatus.ONLINE); + } catch (TeslaPowerwallAuthenticationException e) { + logger.debug("Unexpected authentication error connecting to Tesla Powerwall", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + return; + } catch (TeslaPowerwallCommunicationException e) { + logger.debug("Unexpected error connecting to Tesla Powerwall", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + return; + } + + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_MODE, new StringType(operations.mode)); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_RESERVE, + new QuantityType<>(((operations.reserve / 0.95) - (5 / 0.95)), Units.PERCENT)); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_BATTERY_SOE, + new QuantityType<>(((batterySOE.soe / 0.95) - (5 / 0.95)), Units.PERCENT)); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_GRID_STATUS, + new StringType(gridStatus.gridStatus)); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_GRID_SERVICES, + (gridStatus.gridServices ? OnOffType.ON : OnOffType.OFF)); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_FULL_PACK_ENERGY, + new QuantityType<>(systemStatus.fullPackEnergy, Units.WATT_HOUR)); + if (systemStatus.fullPackEnergy < TeslaPowerwallBindingConstants.TESLA_POWERWALL_CAPACITY) { + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_DEGRADATION, + new QuantityType<>( + (TeslaPowerwallBindingConstants.TESLA_POWERWALL_CAPACITY - systemStatus.fullPackEnergy) + / TeslaPowerwallBindingConstants.TESLA_POWERWALL_CAPACITY * 100, + Units.PERCENT)); + } else { + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_DEGRADATION, + new QuantityType<>(0, Units.PERCENT)); + } + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_GRID_INST_POWER, + new QuantityType<>(meterAggregates.gridInstpower, MetricPrefix.KILO(Units.WATT))); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_GRID_ENERGY_EXPORTED, + new QuantityType<>(meterAggregates.gridEnergyexported, Units.KILOWATT_HOUR)); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_GRID_ENERGY_IMPORTED, + new QuantityType<>(meterAggregates.gridEnergyimported, Units.KILOWATT_HOUR)); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_BATTERY_INST_POWER, + new QuantityType<>(meterAggregates.batteryInstpower, MetricPrefix.KILO(Units.WATT))); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_BATTERY_ENERGY_EXPORTED, + new QuantityType<>(meterAggregates.batteryEnergyexported, Units.KILOWATT_HOUR)); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_BATTERY_ENERGY_IMPORTED, + new QuantityType<>(meterAggregates.batteryEnergyimported, Units.KILOWATT_HOUR)); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_HOME_INST_POWER, + new QuantityType<>(meterAggregates.homeInstpower, MetricPrefix.KILO(Units.WATT))); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_HOME_ENERGY_EXPORTED, + new QuantityType<>(meterAggregates.homeEnergyexported, Units.KILOWATT_HOUR)); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_HOME_ENERGY_IMPORTED, + new QuantityType<>(meterAggregates.homeEnergyimported, Units.KILOWATT_HOUR)); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_SOLAR_INST_POWER, + new QuantityType<>(meterAggregates.solarInstpower, MetricPrefix.KILO(Units.WATT))); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_SOLAR_ENERGY_EXPORTED, + new QuantityType<>(meterAggregates.solarEnergyexported, Units.KILOWATT_HOUR)); + updateState(TeslaPowerwallBindingConstants.CHANNEL_TESLAPOWERWALL_SOLAR_ENERGY_IMPORTED, + new QuantityType<>(meterAggregates.solarEnergyimported, Units.KILOWATT_HOUR)); + } +} diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallHandlerFactory.java b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallHandlerFactory.java new file mode 100644 index 00000000000..96344db1879 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallHandlerFactory.java @@ -0,0 +1,63 @@ +/** + * 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.teslapowerwall.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.core.io.net.http.HttpClientFactory; +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.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link TeslaPowerwallHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Paul Smedley - Initial contribution + */ +@Component(service = ThingHandlerFactory.class, configurationPid = "binding.tesla-powerwall") +@NonNullByDefault +public class TeslaPowerwallHandlerFactory extends BaseThingHandlerFactory { + + private final HttpClient httpClient; + + @Activate + public TeslaPowerwallHandlerFactory(@Reference HttpClientFactory httpClientFactory, + ComponentContext componentContext) { + super.activate(componentContext); + this.httpClient = httpClientFactory.getCommonHttpClient(); + } + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return TeslaPowerwallBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (thingTypeUID.equals(TeslaPowerwallBindingConstants.TESLAPOWERWALL_THING)) { + return new TeslaPowerwallHandler(thing, httpClient); + } + + return null; + } +} diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallTlsTrustManagerProvider.java b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallTlsTrustManagerProvider.java new file mode 100644 index 00000000000..2ae4941e614 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallTlsTrustManagerProvider.java @@ -0,0 +1,40 @@ +/** + * 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.teslapowerwall.internal; + +import javax.net.ssl.X509ExtendedTrustManager; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.io.net.http.TlsTrustManagerProvider; +import org.openhab.core.io.net.http.TrustAllTrustManager; +import org.osgi.service.component.annotations.Component; + +/** + * Provides a TrustManager to allow secure connections to any Tesla Powerwall + * + * @author Paul Smedley - Initial Contribution + */ +@Component +@NonNullByDefault +public class TeslaPowerwallTlsTrustManagerProvider implements TlsTrustManagerProvider { + + @Override + public String getHostName() { + return "powerwall"; + } + + @Override + public X509ExtendedTrustManager getTrustManager() { + return TrustAllTrustManager.getInstance(); + } +} diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallWebTargets.java b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallWebTargets.java new file mode 100644 index 00000000000..eba46fa66a4 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/TeslaPowerwallWebTargets.java @@ -0,0 +1,160 @@ +/** + * 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.teslapowerwall.internal; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; +import org.openhab.binding.teslapowerwall.internal.api.BatterySOE; +import org.openhab.binding.teslapowerwall.internal.api.GridStatus; +import org.openhab.binding.teslapowerwall.internal.api.MeterAggregates; +import org.openhab.binding.teslapowerwall.internal.api.Operations; +import org.openhab.binding.teslapowerwall.internal.api.SystemStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * Handles performing the actual HTTP requests for communicating with Tesla Powerwall units. + * + * @author Paul Smedley - Initial Contribution + * + */ +@NonNullByDefault +public class TeslaPowerwallWebTargets { + private static final int TIMEOUT_MS = 30000; + + private String getBatterySOEUri; + private String getGridStatusUri; + private String getSystemStatusUri; + private String getMeterAggregatesUri; + private String getTokenUri; + private String getOperationUri; + private String token = ""; + private final Logger logger = LoggerFactory.getLogger(TeslaPowerwallWebTargets.class); + private HttpClient httpClient; + + public TeslaPowerwallWebTargets(String hostname, HttpClient httpClient) { + this.httpClient = httpClient; + String baseUri = "https://" + hostname + "/"; + getBatterySOEUri = baseUri + "api/system_status/soe"; + getGridStatusUri = baseUri + "api/system_status/grid_status"; + getSystemStatusUri = baseUri + "api/system_status"; + getMeterAggregatesUri = baseUri + "api/meters/aggregates"; + getTokenUri = baseUri + "api/login/Basic"; + getOperationUri = baseUri + "api/operation"; + } + + public BatterySOE getBatterySOE(String email, String password) + throws TeslaPowerwallCommunicationException, TeslaPowerwallAuthenticationException { + String response = invoke(getBatterySOEUri, email, password); + logger.trace("getBatterySOE response = {}", response); + return BatterySOE.parse(response); + } + + public GridStatus getGridStatus(String email, String password) + throws TeslaPowerwallCommunicationException, TeslaPowerwallAuthenticationException { + String response = invoke(getGridStatusUri, email, password); + logger.trace("getGridStatus response = {}", response); + return GridStatus.parse(response); + } + + public SystemStatus getSystemStatus(String email, String password) + throws TeslaPowerwallCommunicationException, TeslaPowerwallAuthenticationException { + String response = invoke(getSystemStatusUri, email, password); + logger.trace("getSystemStatus response = {}", response); + return SystemStatus.parse(response); + } + + public MeterAggregates getMeterAggregates(String email, String password) + throws TeslaPowerwallCommunicationException, TeslaPowerwallAuthenticationException { + String response = invoke(getMeterAggregatesUri, email, password); + logger.trace("getMeterAggregates response = {}", response); + return MeterAggregates.parse(response); + } + + public Operations getOperations(String email, String password) + throws TeslaPowerwallCommunicationException, TeslaPowerwallAuthenticationException { + String response = invoke(getOperationUri, email, password); + logger.trace("getOperations response = {}", response); + return Operations.parse(response); + } + + public String getToken(String email, String password) + throws TeslaPowerwallCommunicationException, TeslaPowerwallAuthenticationException { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("username", "customer"); + jsonObject.addProperty("password", password); + jsonObject.addProperty("email", email); + jsonObject.addProperty("force_sm_off", false); + logger.debug("logonjson = {}", jsonObject.toString()); + String response = invoke(getTokenUri, HttpMethod.POST, "Content-Type", "application/json", + jsonObject.toString()); + JsonObject jsonResponse = JsonParser.parseString(response).getAsJsonObject(); + String token = jsonResponse.get("token").getAsString(); + logger.debug("Token: {}", token); + return token; + } + + private String invoke(String uri, String email, String password) + throws TeslaPowerwallCommunicationException, TeslaPowerwallAuthenticationException { + if (token.isEmpty()) { + token = getToken(email, password); + } + return invoke(uri, HttpMethod.GET, "Authorization", "Bearer " + token, ""); + } + + private String invoke(String uri, HttpMethod method, String headerKey, String headerValue, String params) + throws TeslaPowerwallCommunicationException, TeslaPowerwallAuthenticationException { + logger.debug("Calling url: {}", uri); + int status = 0; + String jsonResponse = ""; + synchronized (this) { + try { + Request request = httpClient.newRequest(uri).method(method).header(headerKey, headerValue) + .timeout(TIMEOUT_MS, TimeUnit.MILLISECONDS) + .content(new StringContentProvider(params), "application/json"); + if (logger.isTraceEnabled()) { + logger.trace("{} request for {}", method, uri); + } + ContentResponse response = request.send(); + status = response.getStatus(); + jsonResponse = response.getContentAsString(); + if (!jsonResponse.isEmpty()) { + logger.trace("JSON response: '{}'", jsonResponse); + } + if (status == HttpStatus.UNAUTHORIZED_401) { + throw new TeslaPowerwallAuthenticationException("Unauthorized"); + } + if (!HttpStatus.isSuccess(status)) { + throw new TeslaPowerwallCommunicationException( + String.format("Tesla Powerwall returned error <%d> while invoking %s", status, uri)); + } + } catch (TimeoutException | ExecutionException | InterruptedException ex) { + throw new TeslaPowerwallCommunicationException(String.format("{}", ex.getLocalizedMessage(), ex)); + } + } + + return jsonResponse; + } +} diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/BatterySOE.java b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/BatterySOE.java new file mode 100644 index 00000000000..dc59719e9c0 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/BatterySOE.java @@ -0,0 +1,40 @@ +/** + * 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.teslapowerwall.internal.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * Class for holding the set of parameters used to read the battery soe. + * + * @author Paul Smedley - Initial Contribution + * + */ +@NonNullByDefault +public class BatterySOE { + public double soe; + + private BatterySOE() { + } + + public static BatterySOE parse(String response) { + /* parse json string */ + JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject(); + BatterySOE info = new BatterySOE(); + info.soe = jsonObject.get("percentage").getAsDouble(); + return info; + } +} diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/GridStatus.java b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/GridStatus.java new file mode 100644 index 00000000000..9b3671955f9 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/GridStatus.java @@ -0,0 +1,43 @@ +/** + * 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.teslapowerwall.internal.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * Class for holding the set of parameters used to read the battery soe. + * + * @author Paul Smedley - Initial Contribution + * + */ +@NonNullByDefault +public class GridStatus { + + public String gridStatus = ""; + public Boolean gridServices = false; + + private GridStatus() { + } + + public static GridStatus parse(String response) { + /* parse json string */ + JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject(); + GridStatus info = new GridStatus(); + info.gridStatus = jsonObject.get("grid_status").getAsString(); + info.gridServices = jsonObject.get("grid_services_active").getAsString().equalsIgnoreCase("true"); + return info; + } +} diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/MeterAggregates.java b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/MeterAggregates.java new file mode 100644 index 00000000000..07af2bf4cac --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/MeterAggregates.java @@ -0,0 +1,71 @@ +/** + * 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.teslapowerwall.internal.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * Class for holding the set of parameters used to read the battery soe. + * + * @author Paul Smedley - Initial Contribution + * + */ +@NonNullByDefault +public class MeterAggregates { + public double gridInstpower; + public double batteryInstpower; + public double homeInstpower; + public double solarInstpower; + public double gridEnergyexported; + public double batteryEnergyexported; + public double homeEnergyexported; + public double solarEnergyexported; + public double gridEnergyimported; + public double batteryEnergyimported; + public double homeEnergyimported; + public double solarEnergyimported; + + private MeterAggregates() { + } + + public static MeterAggregates parse(String response) { + /* parse json string */ + JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject(); + MeterAggregates info = new MeterAggregates(); + + JsonObject sitejson = jsonObject.get("site").getAsJsonObject(); + info.gridInstpower = sitejson.get("instant_power").getAsDouble() / 1000; + info.gridEnergyexported = sitejson.get("energy_exported").getAsDouble() / 1000; + info.gridEnergyimported = sitejson.get("energy_imported").getAsDouble() / 1000; + + JsonObject batteryjson = jsonObject.get("battery").getAsJsonObject(); + info.batteryInstpower = batteryjson.get("instant_power").getAsDouble() / 1000; + info.batteryEnergyexported = batteryjson.get("energy_exported").getAsDouble() / 1000; + info.batteryEnergyimported = batteryjson.get("energy_imported").getAsDouble() / 1000; + + JsonObject loadjson = jsonObject.get("load").getAsJsonObject(); + info.homeInstpower = loadjson.get("instant_power").getAsDouble() / 1000; + info.homeEnergyexported = loadjson.get("energy_exported").getAsDouble() / 1000; + info.homeEnergyimported = loadjson.get("energy_imported").getAsDouble() / 1000; + + JsonObject solarjson = jsonObject.get("solar").getAsJsonObject(); + info.solarInstpower = solarjson.get("instant_power").getAsDouble() / 1000; + info.solarEnergyexported = solarjson.get("energy_exported").getAsDouble() / 1000; + info.solarEnergyimported = solarjson.get("energy_imported").getAsDouble() / 1000; + + return info; + } +} diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/Operations.java b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/Operations.java new file mode 100644 index 00000000000..7d10171317f --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/Operations.java @@ -0,0 +1,43 @@ +/** + * 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.teslapowerwall.internal.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * Class for holding the set of parameters used to read the battery mode/reserver. + * + * @author Paul Smedley - Initial Contribution + * + */ +@NonNullByDefault +public class Operations { + + public String mode = ""; + public double reserve; + + private Operations() { + } + + public static Operations parse(String response) { + /* parse json string */ + JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject(); + Operations info = new Operations(); + info.mode = jsonObject.get("real_mode").getAsString(); + info.reserve = jsonObject.get("backup_reserve_percent").getAsDouble(); + return info; + } +} diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/SystemStatus.java b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/SystemStatus.java new file mode 100644 index 00000000000..90ec3bed241 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/java/org/openhab/binding/teslapowerwall/internal/api/SystemStatus.java @@ -0,0 +1,40 @@ +/** + * 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.teslapowerwall.internal.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * Class for holding the set of parameters used to read the battery soe. + * + * @author Paul Smedley - Initial Contribution + * + */ +@NonNullByDefault +public class SystemStatus { + public double fullPackEnergy; + + private SystemStatus() { + } + + public static SystemStatus parse(String response) { + /* parse json string */ + JsonObject jsonObject = JsonParser.parseString(response).getAsJsonObject(); + SystemStatus info = new SystemStatus(); + info.fullPackEnergy = jsonObject.get("nominal_full_pack_energy").getAsDouble(); + return info; + } +} diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.teslapowerwall/src/main/resources/OH-INF/addon/addon.xml new file mode 100644 index 00000000000..38be32cd248 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/resources/OH-INF/addon/addon.xml @@ -0,0 +1,11 @@ + + + + binding + Tesla Powerwall Binding + This is the binding for Tesla Powerwall. + local + + diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/resources/OH-INF/i18n/teslapowerwall.properties b/bundles/org.openhab.binding.teslapowerwall/src/main/resources/OH-INF/i18n/teslapowerwall.properties new file mode 100644 index 00000000000..564a9675eff --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/resources/OH-INF/i18n/teslapowerwall.properties @@ -0,0 +1,181 @@ +# add-on + +addon.teslapowerwall.name = Tesla Powerwall Binding +addon.teslapowerwall.description = This is the binding for Tesla Powerwall. + +# thing types + +thing-type.teslapowerwall.tesla-powerwall.label = Tesla Powerwall +thing-type.teslapowerwall.tesla-powerwall.description = Tesla Powerwall + +# thing types config + +thing-type.config.teslapowerwall.tesla-powerwall.email.label = Email +thing-type.config.teslapowerwall.tesla-powerwall.email.description = Local Powerwall account email +thing-type.config.teslapowerwall.tesla-powerwall.hostname.label = Hostname/IP Address +thing-type.config.teslapowerwall.tesla-powerwall.hostname.description = The host name or IP address of the Tesla Powerwall. +thing-type.config.teslapowerwall.tesla-powerwall.password.label = Password +thing-type.config.teslapowerwall.tesla-powerwall.password.description = Local Powerwall account password +thing-type.config.teslapowerwall.tesla-powerwall.refresh.label = Refresh Interval +thing-type.config.teslapowerwall.tesla-powerwall.refresh.description = Specifies the refresh interval in seconds. + +# channel types + +channel-type.teslapowerwall.battery-energy-exported.label = Battery Energy Exported +channel-type.teslapowerwall.battery-energy-exported.description = Total battery energy exported +channel-type.teslapowerwall.battery-energy-imported.label = Battery Energy Imported +channel-type.teslapowerwall.battery-energy-imported.description = Total battery energy imported +channel-type.teslapowerwall.battery-inst-power.label = Instant Battery Power +channel-type.teslapowerwall.battery-inst-power.description = Instantaneous battery power supply +channel-type.teslapowerwall.battery-soe.label = Battery SOE +channel-type.teslapowerwall.battery-soe.description = Current battery state of charge +channel-type.teslapowerwall.degradation.label = Battery Degradation +channel-type.teslapowerwall.degradation.description = Current battery degradation %, based on 13.5kW full capacity +channel-type.teslapowerwall.full-pack-energy.label = Battery Full Pack Energy +channel-type.teslapowerwall.full-pack-energy.description = Battery full pack energy +channel-type.teslapowerwall.grid-energy-exported.label = Grid Energy Exported +channel-type.teslapowerwall.grid-energy-exported.description = Total grid energy exported +channel-type.teslapowerwall.grid-energy-imported.label = Grid Energy Imported +channel-type.teslapowerwall.grid-energy-imported.description = Total grid energy imported +channel-type.teslapowerwall.grid-inst-power.label = Instant Grid Power +channel-type.teslapowerwall.grid-inst-power.description = Instantaneous grid power supply +channel-type.teslapowerwall.grid-services.label = Grid Services +channel-type.teslapowerwall.grid-services.description = Grid services activation status +channel-type.teslapowerwall.grid-status.label = Grid Status +channel-type.teslapowerwall.grid-status.description = Current status of the power grid +channel-type.teslapowerwall.home-energy-exported.label = Home Energy Exported +channel-type.teslapowerwall.home-energy-exported.description = Total home energy exported +channel-type.teslapowerwall.home-energy-imported.label = Home Energy Imported +channel-type.teslapowerwall.home-energy-imported.description = Total home energy emported +channel-type.teslapowerwall.home-inst-power.label = Instant Home Power +channel-type.teslapowerwall.home-inst-power.description = Instantaneous home power supply +channel-type.teslapowerwall.mode.label = Operating Mode +channel-type.teslapowerwall.mode.description = Current operating mode +channel-type.teslapowerwall.reserve.label = Battery Reserve +channel-type.teslapowerwall.reserve.description = Current battery reserve % +channel-type.teslapowerwall.solar-energy-exported.label = Solar Energy Exported +channel-type.teslapowerwall.solar-energy-exported.description = Total solar energy exported +channel-type.teslapowerwall.solar-energy-imported.label = Solar Energy Imported +channel-type.teslapowerwall.solar-energy-imported.description = Total solar energy imported +channel-type.teslapowerwall.solar-inst-power.label = Instant Solar Power +channel-type.teslapowerwall.solar-inst-power.description = Instantaneous solar power supply + +# channel types + +channel-type.teslapowerwall.tesla-powerwall-battery-energyexported.label = Battery Energy Exported +channel-type.teslapowerwall.tesla-powerwall-battery-energyexported.description = Total Battery Energy Exported +channel-type.teslapowerwall.tesla-powerwall-battery-energyimported.label = Battery Energy Imported +channel-type.teslapowerwall.tesla-powerwall-battery-energyimported.description = Total Battery Energy Imported +channel-type.teslapowerwall.tesla-powerwall-battery-instpower.label = Instant Battery Power +channel-type.teslapowerwall.tesla-powerwall-battery-instpower.description = Instantaneous Battery Power Supply +channel-type.teslapowerwall.tesla-powerwall-batterysoe.label = Battery SOE +channel-type.teslapowerwall.tesla-powerwall-batterysoe.description = Current battery state of charge +channel-type.teslapowerwall.tesla-powerwall-degradation.label = Battery Degradation +channel-type.teslapowerwall.tesla-powerwall-degradation.description = Current battery degradation %, based on 13.5kW full capacity +channel-type.teslapowerwall.tesla-powerwall-full-pack-energy.label = Battery Full Pack Energy +channel-type.teslapowerwall.tesla-powerwall-full-pack-energy.description = Battery Full Pack Energy +channel-type.teslapowerwall.tesla-powerwall-grid-energyexported.label = Grid Energy Exported +channel-type.teslapowerwall.tesla-powerwall-grid-energyexported.description = Total Grid Energy Exported +channel-type.teslapowerwall.tesla-powerwall-grid-energyimported.label = Grid Energy Imported +channel-type.teslapowerwall.tesla-powerwall-grid-energyimported.description = Total Grid Energy Imported +channel-type.teslapowerwall.tesla-powerwall-grid-instpower.label = Instant Grid Power +channel-type.teslapowerwall.tesla-powerwall-grid-instpower.description = Instantaneous Grid Power Supply +channel-type.teslapowerwall.tesla-powerwall-gridservices.label = Grid Services +channel-type.teslapowerwall.tesla-powerwall-gridservices.description = Grid Services Activation Status +channel-type.teslapowerwall.tesla-powerwall-gridstatus.label = Grid Status +channel-type.teslapowerwall.tesla-powerwall-gridstatus.description = Current status of the Power Grid +channel-type.teslapowerwall.tesla-powerwall-home-energyexported.label = Home Energy Exported +channel-type.teslapowerwall.tesla-powerwall-home-energyexported.description = Total Home Energy Exported +channel-type.teslapowerwall.tesla-powerwall-home-energyimported.label = Home Energy Imported +channel-type.teslapowerwall.tesla-powerwall-home-energyimported.description = Total Home Energy Imported +channel-type.teslapowerwall.tesla-powerwall-home-instpower.label = Instant Home Power +channel-type.teslapowerwall.tesla-powerwall-home-instpower.description = Instantaneous Home Power Supply +channel-type.teslapowerwall.tesla-powerwall-mode.label = Operating Mode +channel-type.teslapowerwall.tesla-powerwall-mode.description = Current operating mode +channel-type.teslapowerwall.tesla-powerwall-reserve.label = Battery Reserve +channel-type.teslapowerwall.tesla-powerwall-reserve.description = Current battery reserve % +channel-type.teslapowerwall.tesla-powerwall-solar-energyexported.label = Solar Energy Exported +channel-type.teslapowerwall.tesla-powerwall-solar-energyexported.description = Total Solar Energy Exported +channel-type.teslapowerwall.tesla-powerwall-solar-energyimported.label = Solar Energy Imported +channel-type.teslapowerwall.tesla-powerwall-solar-energyimported.description = Total Solar Energy Imported +channel-type.teslapowerwall.tesla-powerwall-solar-instpower.label = Instant Solar Power +channel-type.teslapowerwall.tesla-powerwall-solar-instpower.description = Instantaneous Solar Power Supply + +# thing types config + +thing-type.config.tesla-powerwall.tesla-powerwall.email.label = Local Powerwall Login Email +thing-type.config.tesla-powerwall.tesla-powerwall.email.description = Local Powerwall Account Email +thing-type.config.tesla-powerwall.tesla-powerwall.hostname.label = Hostname/IP Address +thing-type.config.tesla-powerwall.tesla-powerwall.hostname.description = The host name or IP address of the Tesla Powerwall. +thing-type.config.tesla-powerwall.tesla-powerwall.password.label = Local Powerwall Login Password +thing-type.config.tesla-powerwall.tesla-powerwall.password.description = Local Powerwall Account Password +thing-type.config.tesla-powerwall.tesla-powerwall.refresh.label = Refresh Interval +thing-type.config.tesla-powerwall.tesla-powerwall.refresh.description = Specifies the refresh interval in seconds. + +# channel types + +channel-type.teslapowerwall.teslapowerwall-battery-energyexported.label = Battery Energy Exported +channel-type.teslapowerwall.teslapowerwall-battery-energyexported.description = Total Battery Energy Exported +channel-type.teslapowerwall.teslapowerwall-battery-energyimported.label = Battery Energy Imported +channel-type.teslapowerwall.teslapowerwall-battery-energyimported.description = Total Battery Energy Imported +channel-type.teslapowerwall.teslapowerwall-degradation.label = Battery Degradation +channel-type.teslapowerwall.teslapowerwall-degradation.description = Current battery degradation %, based on 13.5kW full capacity +channel-type.teslapowerwall.teslapowerwall-full-pack-energy.label = Battery Full Pack Energy +channel-type.teslapowerwall.teslapowerwall-full-pack-energy.description = Battery Full Pack Energy +channel-type.teslapowerwall.teslapowerwall-grid-energyexported.label = Grid Energy Exported +channel-type.teslapowerwall.teslapowerwall-grid-energyexported.description = Total Grid Energy Exported +channel-type.teslapowerwall.teslapowerwall-grid-energyimported.label = Grid Energy Imported +channel-type.teslapowerwall.teslapowerwall-grid-energyimported.description = Total Grid Energy Imported +channel-type.teslapowerwall.teslapowerwall-home-energyexported.label = Home Energy Exported +channel-type.teslapowerwall.teslapowerwall-home-energyexported.description = Total Home Energy Exported +channel-type.teslapowerwall.teslapowerwall-home-energyimported.label = Home Energy Imported +channel-type.teslapowerwall.teslapowerwall-home-energyimported.description = Total Home Energy Imported +channel-type.teslapowerwall.teslapowerwall-solar-energyexported.label = Solar Energy Exported +channel-type.teslapowerwall.teslapowerwall-solar-energyexported.description = Total Solar Energy Exported +channel-type.teslapowerwall.teslapowerwall-solar-energyimported.label = Solar Energy Imported +channel-type.teslapowerwall.teslapowerwall-solar-energyimported.description = Total Solar Energy Imported +channel-type.teslapowerwall.teslapowerwall-solar-instpower.label = Instant Solar Power +channel-type.teslapowerwall.teslapowerwall-solar-instpower.description = Instantaneous Solar Power Supply + +# channel types + +channel-type.teslapowerwall.teslapowerwall-battery_energyexported.label = Battery Energy Exported +channel-type.teslapowerwall.teslapowerwall-battery_energyexported.description = Total Battery Energy Exported +channel-type.teslapowerwall.teslapowerwall-battery_energyimported.label = Battery Energy Imported +channel-type.teslapowerwall.teslapowerwall-battery_energyimported.description = Total Battery Energy Imported +channel-type.teslapowerwall.teslapowerwall-battery_instpower.label = Instant Battery Power +channel-type.teslapowerwall.teslapowerwall-battery_instpower.description = Instantaneous Battery Power Supply +channel-type.teslapowerwall.teslapowerwall-batterysoe.label = Battery SOE +channel-type.teslapowerwall.teslapowerwall-batterysoe.description = Current battery state of charge +channel-type.teslapowerwall.teslapowerwall-full_pack_energy.label = Battery Full Pack Energy +channel-type.teslapowerwall.teslapowerwall-full_pack_energy.description = Battery Full Pack Energy +channel-type.teslapowerwall.teslapowerwall-grid_energyexported.label = Grid Energy Exported +channel-type.teslapowerwall.teslapowerwall-grid_energyexported.description = Total Grid Energy Exported +channel-type.teslapowerwall.teslapowerwall-grid_energyimported.label = Grid Energy Imported +channel-type.teslapowerwall.teslapowerwall-grid_energyimported.description = Total Grid Energy Imported +channel-type.teslapowerwall.teslapowerwall-grid_instpower.label = Instant Grid Power +channel-type.teslapowerwall.teslapowerwall-grid_instpower.description = Instantaneous Grid Power Supply +channel-type.teslapowerwall.teslapowerwall-gridservices.label = Grid Services +channel-type.teslapowerwall.teslapowerwall-gridservices.description = Grid Services Activation Status +channel-type.teslapowerwall.teslapowerwall-gridstatus.label = Grid Status +channel-type.teslapowerwall.teslapowerwall-gridstatus.description = Current status of the Power Grid +channel-type.teslapowerwall.teslapowerwall-home_energyexported.label = Home Energy Exported +channel-type.teslapowerwall.teslapowerwall-home_energyexported.description = Total Home Energy Exported +channel-type.teslapowerwall.teslapowerwall-home_energyimported.label = Home Energy Imported +channel-type.teslapowerwall.teslapowerwall-home_energyimported.description = Total Home Energy Imported +channel-type.teslapowerwall.teslapowerwall-home_instpower.label = Instant Home Power +channel-type.teslapowerwall.teslapowerwall-home_instpower.description = Instantaneous Home Power Supply +channel-type.teslapowerwall.teslapowerwall-mode.label = Operating Mode +channel-type.teslapowerwall.teslapowerwall-mode.description = Current operating mode +channel-type.teslapowerwall.teslapowerwall-reserve.label = Battery Reserve +channel-type.teslapowerwall.teslapowerwall-reserve.description = Current battery reserve % +channel-type.teslapowerwall.teslapowerwall-solar_energyexported.label = Solar Energy Exported +channel-type.teslapowerwall.teslapowerwall-solar_energyexported.description = Total Solar Energy Exported +channel-type.teslapowerwall.teslapowerwall-solar_energyimported.label = Solar Energy Imported +channel-type.teslapowerwall.teslapowerwall-solar_energyimported.description = Total Solar Energy Imported +channel-type.teslapowerwall.teslapowerwall-solar_instpower.label = Instant Solar Power +channel-type.teslapowerwall.teslapowerwall-solar_instpower.description = Instantaneous Solar Power Supply + +# thing status descriptions + +offline.conf-error.missing-config-key = Mandatory configuration items must be set diff --git a/bundles/org.openhab.binding.teslapowerwall/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.teslapowerwall/src/main/resources/OH-INF/thing/thing-types.xml new file mode 100644 index 00000000000..be3c00646a0 --- /dev/null +++ b/bundles/org.openhab.binding.teslapowerwall/src/main/resources/OH-INF/thing/thing-types.xml @@ -0,0 +1,170 @@ + + + + + + Tesla Powerwall + + + + + + + + + + + + + + + + + + + + + + + + + + + The host name or IP address of the Tesla Powerwall. + + + + Local Powerwall account email + + + + Local Powerwall account password + password + + + + Specifies the refresh interval in seconds. + 10 + + + + + + + String + + Current status of the power grid + + + + Switch + + Grid services activation status + + + + Number:Dimensionless + + Current battery state of charge + + + + String + + Current operating mode + + + Number:Dimensionless + + Current battery reserve % + + + + Number:Power + + Instantaneous grid power supply + + + + Number:Power + + Instantaneous battery power supply + + + + Number:Power + + Instantaneous home power supply + + + + Number:Power + + Instantaneous solar power supply + + + + Number:Energy + + Total grid energy exported + + + + Number:Energy + + Total battery energy exported + + + + Number:Energy + + Total home energy exported + + + + Number:Energy + + Total solar energy exported + + + + Number:Energy + + Total grid energy imported + + + + Number:Energy + + Total battery energy imported + + + + Number:Energy + + Total home energy emported + + + + Number:Energy + + Total solar energy imported + + + + Number:Energy + + Battery full pack energy + + + + Number:Dimensionless + + Current battery degradation %, based on 13.5kW full capacity + + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 8eb9e1456cd..b51354c2c24 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -402,6 +402,7 @@ org.openhab.binding.teleinfo org.openhab.binding.tellstick org.openhab.binding.tesla + org.openhab.binding.teslapowerwall org.openhab.binding.tibber org.openhab.binding.tivo org.openhab.binding.touchwand