diff --git a/bundles/org.openhab.binding.enphase/NOTICE b/bundles/org.openhab.binding.enphase/NOTICE
index 38d625e34..0ca708bef 100644
--- a/bundles/org.openhab.binding.enphase/NOTICE
+++ b/bundles/org.openhab.binding.enphase/NOTICE
@@ -11,3 +11,10 @@ https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons
+
+== Third-party Content
+
+jsoup
+* License: MIT License
+* Project: https://jsoup.org/
+* Source: https://github.com/jhy/jsoup
diff --git a/bundles/org.openhab.binding.enphase/README.md b/bundles/org.openhab.binding.enphase/README.md
index 74a71d879..94bbef2a6 100644
--- a/bundles/org.openhab.binding.enphase/README.md
+++ b/bundles/org.openhab.binding.enphase/README.md
@@ -22,18 +22,47 @@ The binding auto detects which data is available and will report this in the log
## Discovery
The binding can discover Envoy gateways, micro inverters and relays.
+If login access is needed the Bridge `envoy` needs to be configured after discovering and adding before other things can be discovered.
+In that case, after configuring the login, run discovery again.
## Thing Configuration
-The Envoy gateway thing `envoy` has the following configuration options:
+### Bridge configuration
+
+Depending on the software version of the Envoy gateway thing `envoy` there are different configuration options needed.
+Newer versions of the Envoy software (> version 7) require a different authentication method.
+Because the configuration is different, different bridge things are available.
+
+The following options are relevant for all envoy versions:
| parameter | required | description |
|--------------|----------|-------------------------------------------------------------------------------------------------------------|
| serialNumber | yes | The serial number of the Envoy gateway which can be found on the gateway |
| hostname | no | The host name/ip address of the Envoy gateway. Leave empty to auto detect |
+| refresh | no | Period between data updates. The default is the same 5 minutes the data is actually refreshed on the Envoy |
+
+#### Envoy below version 7
+
+For Envoy versions below 7 has the following authentication configuration options are relevant:
+
+| parameter | required | description |
+|--------------|----------|-------------------------------------------------------------------------------------------------------------|
| username | no | The user name to the Envoy gateway. Leave empty when using the default user name |
| password | no | The password to the Envoy gateway. Leave empty when using the default password |
-| refresh | no | Period between data updates. The default is the same 5 minutes the data is actual refreshed on the Envoy |
+
+#### Envoy from version 7
+
+For Envoy versions 7 and newer has the following authentication configuration options are relevant:
+
+| parameter | required | description |
+|--------------|----------|-------------------------------------------------------------------------------------------------------------|
+| autoJwt | yes | Specify if the JWT access token should be obtained by logging in or if the jwt is provided manually |
+| username | yes/no | The user name to the Entrez server. Required if auto Jwt is true |
+| password | yes/no | The password to the Entrez server. Required if auto Jwt is true |
+| siteName | yes/no | The name of the site. Can be found above the Site Id in the Enphase app. Required when autoJwt is true |
+| jwt | yes/no | The jwt is required if autoJWT is false, if it's true jwt is not used |
+
+### Thing configuration
The micro inverter `inverter` and `relay` things have only 1 parameter:
diff --git a/bundles/org.openhab.binding.enphase/pom.xml b/bundles/org.openhab.binding.enphase/pom.xml
index 635670a5a..6ddbc7205 100644
--- a/bundles/org.openhab.binding.enphase/pom.xml
+++ b/bundles/org.openhab.binding.enphase/pom.xml
@@ -14,4 +14,17 @@
openHAB Add-ons :: Bundles :: Enphase Binding
+
+ 1.15.3
+
+
+
+
+ org.jsoup
+ jsoup
+ ${jsoup.version}
+ provided
+
+
+
diff --git a/bundles/org.openhab.binding.enphase/src/main/feature/feature.xml b/bundles/org.openhab.binding.enphase/src/main/feature/feature.xml
index 537cf0dc5..82dcdc4ab 100644
--- a/bundles/org.openhab.binding.enphase/src/main/feature/feature.xml
+++ b/bundles/org.openhab.binding.enphase/src/main/feature/feature.xml
@@ -5,6 +5,7 @@
openhab-runtime-base
+ mvn:org.jsoup/jsoup/1.15.3
mvn:org.openhab.addons.bundles/org.openhab.binding.enphase/${project.version}
diff --git a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnphaseHandlerFactory.java b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnphaseHandlerFactory.java
index 3d05c6f21..dc6f8f3a4 100644
--- a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnphaseHandlerFactory.java
+++ b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnphaseHandlerFactory.java
@@ -12,19 +12,21 @@
*/
package org.openhab.binding.enphase.internal;
-import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.*;
+import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.THING_TYPE_ENPHASE_ENVOY;
+import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.THING_TYPE_ENPHASE_INVERTER;
+import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.THING_TYPE_ENPHASE_RELAY;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.openhab.binding.enphase.internal.handler.EnphaseInverterHandler;
import org.openhab.binding.enphase.internal.handler.EnphaseRelayHandler;
import org.openhab.binding.enphase.internal.handler.EnvoyBridgeHandler;
import org.openhab.core.i18n.LocaleProvider;
import org.openhab.core.i18n.TranslationProvider;
-import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
@@ -33,11 +35,11 @@ import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
/**
- * The {@link EnphaseHandlerFactory} is responsible for creating things and thing
- * handlers.
+ * The {@link EnphaseHandlerFactory} is responsible for creating things and thing handlers.
*
* @author Hilbrand Bouwkamp - Initial contribution
*/
@@ -49,16 +51,33 @@ public class EnphaseHandlerFactory extends BaseThingHandlerFactory {
THING_TYPE_ENPHASE_INVERTER, THING_TYPE_ENPHASE_RELAY);
private final MessageTranslator messageTranslator;
- private final HttpClient commonHttpClient;
private final EnvoyHostAddressCache envoyHostAddressCache;
+ private final HttpClient httpClient;
@Activate
public EnphaseHandlerFactory(final @Reference LocaleProvider localeProvider,
- final @Reference TranslationProvider i18nProvider, final @Reference HttpClientFactory httpClientFactory,
- @Reference final EnvoyHostAddressCache envoyHostAddressCache) {
+ final @Reference TranslationProvider i18nProvider,
+ final @Reference EnvoyHostAddressCache envoyHostAddressCache) {
messageTranslator = new MessageTranslator(localeProvider, i18nProvider);
- commonHttpClient = httpClientFactory.getCommonHttpClient();
this.envoyHostAddressCache = envoyHostAddressCache;
+ // Note: Had to switch to using a locally generated httpClient as
+ // the Envoy server went to a self-signed SSL connection and this
+ // was the only way to set the client to ignore SSL errors
+ this.httpClient = new HttpClient(new SslContextFactory.Client(true));
+ startHttpClient();
+ }
+
+ private void startHttpClient() {
+ try {
+ httpClient.start();
+ } catch (final Exception ex) {
+ throw new IllegalStateException("Could not start HttpClient.", ex);
+ }
+ }
+
+ @Deactivate
+ public void deactivate() {
+ httpClient.destroy();
}
@Override
@@ -71,7 +90,7 @@ public class EnphaseHandlerFactory extends BaseThingHandlerFactory {
final ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_ENPHASE_ENVOY.equals(thingTypeUID)) {
- return new EnvoyBridgeHandler((Bridge) thing, commonHttpClient, envoyHostAddressCache);
+ return new EnvoyBridgeHandler((Bridge) thing, httpClient, envoyHostAddressCache);
} else if (THING_TYPE_ENPHASE_INVERTER.equals(thingTypeUID)) {
return new EnphaseInverterHandler(thing, messageTranslator);
} else if (THING_TYPE_ENPHASE_RELAY.equals(thingTypeUID)) {
diff --git a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnvoyConfiguration.java b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnvoyConfiguration.java
index a39cada63..0a1b862bf 100644
--- a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnvoyConfiguration.java
+++ b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnvoyConfiguration.java
@@ -29,6 +29,9 @@ public class EnvoyConfiguration {
public String hostname = "";
public String username = DEFAULT_USERNAME;
public String password = "";
+ public String jwt = "";
+ public boolean autoJwt = true;
+ public String siteName = "";
public int refresh = DEFAULT_REFRESH_MINUTES;
@Override
diff --git a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/discovery/EnphaseDevicesDiscoveryService.java b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/discovery/EnphaseDevicesDiscoveryService.java
index bf664b17f..dc8efb82f 100644
--- a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/discovery/EnphaseDevicesDiscoveryService.java
+++ b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/discovery/EnphaseDevicesDiscoveryService.java
@@ -12,7 +12,9 @@
*/
package org.openhab.binding.enphase.internal.discovery;
-import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.*;
+import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.CONFIG_SERIAL_NUMBER;
+import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.THING_TYPE_ENPHASE_INVERTER;
+import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.THING_TYPE_ENPHASE_RELAY;
import java.util.HashMap;
import java.util.Map;
@@ -21,6 +23,7 @@ import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.enphase.internal.EnphaseBindingConstants;
import org.openhab.binding.enphase.internal.EnphaseBindingConstants.EnphaseDeviceType;
import org.openhab.binding.enphase.internal.dto.InventoryJsonDTO.DeviceDTO;
import org.openhab.binding.enphase.internal.dto.InverterDTO;
@@ -124,7 +127,7 @@ public class EnphaseDevicesDiscoveryService extends AbstractDiscoveryService
private void discover(final ThingUID bridgeID, final String serialNumber, final ThingTypeUID typeUID,
final String label) {
- final String shortSerialNumber = defaultPassword(serialNumber);
+ final String shortSerialNumber = EnphaseBindingConstants.defaultPassword(serialNumber);
final ThingUID thingUID = new ThingUID(typeUID, bridgeID, shortSerialNumber);
final Map properties = new HashMap<>(1);
diff --git a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/discovery/EnvoyDiscoveryParticipant.java b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/discovery/EnvoyDiscoveryParticipant.java
index 02fe022f2..f0e17c58b 100644
--- a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/discovery/EnvoyDiscoveryParticipant.java
+++ b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/discovery/EnvoyDiscoveryParticipant.java
@@ -12,7 +12,12 @@
*/
package org.openhab.binding.enphase.internal.discovery;
-import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.*;
+import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.CONFIG_HOSTNAME;
+import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.CONFIG_SERIAL_NUMBER;
+import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.DISCOVERY_SERIAL;
+import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.DISCOVERY_VERSION;
+import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.PROPERTY_VERSION;
+import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.THING_TYPE_ENPHASE_ENVOY;
import java.net.Inet4Address;
import java.util.HashMap;
@@ -38,8 +43,7 @@ import org.slf4j.LoggerFactory;
/**
* MDNS discovery participant for discovering Envoy gateways.
* This service also keeps track of any discovered Envoys host name to provide this information for existing Envoy
- * bridges
- * so the bridge cat get the host name/ip address if that is unknown.
+ * bridges so the bridge cat get the host name/ip address if that is unknown.
*
* @author Thomas Hentschel - Initial contribution
* @author Hilbrand Bouwkamp - Initial contribution
@@ -55,7 +59,7 @@ public class EnvoyDiscoveryParticipant implements MDNSDiscoveryParticipant, Envo
@Override
public Set getSupportedThingTypeUIDs() {
- return Set.of(EnphaseBindingConstants.THING_TYPE_ENPHASE_ENVOY);
+ return Set.of(THING_TYPE_ENPHASE_ENVOY);
}
@Override
@@ -101,7 +105,7 @@ public class EnvoyDiscoveryParticipant implements MDNSDiscoveryParticipant, Envo
properties.put(PROPERTY_VERSION, version);
return DiscoveryResultBuilder.create(uid).withProperties(properties)
.withRepresentationProperty(CONFIG_SERIAL_NUMBER)
- .withLabel("Enphase Envoy " + defaultPassword(serialNumber)).build();
+ .withLabel("Enphase Envoy " + EnphaseBindingConstants.defaultPassword(serialNumber)).build();
}
@Override
diff --git a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/dto/EntrezJwtDTO.java b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/dto/EntrezJwtDTO.java
new file mode 100644
index 000000000..00fb40df5
--- /dev/null
+++ b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/dto/EntrezJwtDTO.java
@@ -0,0 +1,137 @@
+/**
+ * Copyright (c) 2010-2023 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.enphase.internal.dto;
+
+/**
+ * Data class for Enphase Entrez Portal.
+ *
+ * @author Joe Inkenbrandt - Initial contribution
+ */
+public class EntrezJwtDTO {
+
+ public class EntrezJwtHeaderDTO {
+ private String kid;
+ private String typ;
+ private String alg;
+
+ public String getKid() {
+ return kid;
+ }
+
+ public void setKid(final String kid) {
+ this.kid = kid;
+ }
+
+ public String getTyp() {
+ return typ;
+ }
+
+ public void setTyp(final String typ) {
+ this.typ = typ;
+ }
+
+ public String getAlg() {
+ return alg;
+ }
+
+ public void setAlg(final String alg) {
+ this.alg = alg;
+ }
+ }
+
+ public class EntrezJwtBodyDTO {
+ private String aud;
+ private String iss;
+ private String enphaseUser;
+ private Long exp;
+ private Long iat;
+ private String jti;
+ private String username;
+
+ public String getAud() {
+ return aud;
+ }
+
+ public void setAud(final String aud) {
+ this.aud = aud;
+ }
+
+ public String getIss() {
+ return iss;
+ }
+
+ public void setIss(final String iss) {
+ this.iss = iss;
+ }
+
+ public String getEnphaseUser() {
+ return enphaseUser;
+ }
+
+ public void setEnphaseUser(final String enphaseUser) {
+ this.enphaseUser = enphaseUser;
+ }
+
+ public Long getExp() {
+ return exp;
+ }
+
+ public void setExp(final Long exp) {
+ this.exp = exp;
+ }
+
+ public Long getIat() {
+ return iat;
+ }
+
+ public void setIat(final Long iat) {
+ this.iat = iat;
+ }
+
+ public String getJti() {
+ return jti;
+ }
+
+ public void setJti(final String jti) {
+ this.jti = jti;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(final String username) {
+ this.username = username;
+ }
+ }
+
+ private final EntrezJwtHeaderDTO header;
+ private final EntrezJwtBodyDTO body;
+
+ public EntrezJwtDTO(final EntrezJwtHeaderDTO header, final EntrezJwtBodyDTO body) {
+ this.header = header;
+ this.body = body;
+ }
+
+ public boolean isValid() {
+ return header == null || body == null;
+ }
+
+ public EntrezJwtBodyDTO getBody() {
+ return body;
+ }
+
+ public EntrezJwtHeaderDTO getHeader() {
+ return header;
+ }
+}
diff --git a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EnphaseException.java b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EnphaseException.java
new file mode 100644
index 000000000..2038bcba4
--- /dev/null
+++ b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EnphaseException.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2023 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.enphase.internal.exception;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ *
+ *
+ * @author Hilbrand Bouwkamp - Initial contribution
+ */
+@NonNullByDefault
+public class EnphaseException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ public EnphaseException(final String message) {
+ super(message);
+ }
+
+ public EnphaseException(final String message, final @Nullable Throwable throwable) {
+ super(message, throwable);
+ }
+}
diff --git a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EntrezConnectionException.java b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EntrezConnectionException.java
new file mode 100644
index 000000000..74fd730ca
--- /dev/null
+++ b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EntrezConnectionException.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2023 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.enphase.internal.exception;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * Exception thrown when a connection problem occurs to the Entrez portal.
+ *
+ * @author Hilbrand Bouwkamp - Initial contribution
+ */
+@NonNullByDefault
+public class EntrezConnectionException extends EnphaseException {
+
+ private static final long serialVersionUID = 1L;
+
+ public EntrezConnectionException(final String message) {
+ super(message);
+ }
+
+ public EntrezConnectionException(final String message, final @Nullable Throwable e) {
+ super(message + (e == null ? "" : e.getMessage()), e);
+ }
+}
diff --git a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EntrezJwtInvalidException.java b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EntrezJwtInvalidException.java
new file mode 100644
index 000000000..06787d8cc
--- /dev/null
+++ b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EntrezJwtInvalidException.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2023 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.enphase.internal.exception;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Exception thrown when the JWT access token is invalid.
+ *
+ * @author Hilbrand Bouwkamp - Initial contribution
+ */
+@NonNullByDefault
+public class EntrezJwtInvalidException extends EnphaseException {
+
+ private static final long serialVersionUID = 1L;
+
+ public EntrezJwtInvalidException(final String message) {
+ super(message);
+ }
+}
diff --git a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnvoyConnectionException.java b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EnvoyConnectionException.java
similarity index 88%
rename from bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnvoyConnectionException.java
rename to bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EnvoyConnectionException.java
index 66d3bdf57..20bc93596 100644
--- a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnvoyConnectionException.java
+++ b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EnvoyConnectionException.java
@@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.openhab.binding.enphase.internal;
+package org.openhab.binding.enphase.internal.exception;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
@@ -21,7 +21,7 @@ import org.eclipse.jdt.annotation.Nullable;
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
-public class EnvoyConnectionException extends Exception {
+public class EnvoyConnectionException extends EnphaseException {
private static final long serialVersionUID = 1L;
diff --git a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnvoyNoHostnameException.java b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EnvoyNoHostnameException.java
similarity index 86%
rename from bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnvoyNoHostnameException.java
rename to bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EnvoyNoHostnameException.java
index 9cb1dcb3b..936154442 100644
--- a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/EnvoyNoHostnameException.java
+++ b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/exception/EnvoyNoHostnameException.java
@@ -10,7 +10,7 @@
*
* SPDX-License-Identifier: EPL-2.0
*/
-package org.openhab.binding.enphase.internal;
+package org.openhab.binding.enphase.internal.exception;
import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -20,7 +20,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
* @author Hilbrand Bouwkamp - Initial contribution
*/
@NonNullByDefault
-public class EnvoyNoHostnameException extends Exception {
+public class EnvoyNoHostnameException extends EnphaseException {
private static final long serialVersionUID = 1L;
diff --git a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/handler/EntrezConnector.java b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/handler/EntrezConnector.java
new file mode 100644
index 000000000..bdec6d997
--- /dev/null
+++ b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/handler/EntrezConnector.java
@@ -0,0 +1,157 @@
+/**
+ * Copyright (c) 2010-2023 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.enphase.internal.handler;
+
+import java.net.HttpCookie;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.List;
+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.FormContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.util.Fields;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+import org.openhab.binding.enphase.internal.dto.EntrezJwtDTO;
+import org.openhab.binding.enphase.internal.dto.EntrezJwtDTO.EntrezJwtBodyDTO;
+import org.openhab.binding.enphase.internal.dto.EntrezJwtDTO.EntrezJwtHeaderDTO;
+import org.openhab.binding.enphase.internal.exception.EntrezConnectionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * Connector logic for connecting to Entrez server
+ *
+ * @author Joe Inkenbrandt - Initial contribution
+ */
+@NonNullByDefault
+public class EntrezConnector {
+
+ private static final String SESSION_COOKIE_NAME = "SESSION";
+ private static final String ELEMENT_ID_JWT_TOKEN = "#JWTToken";
+ private static final String LOGIN_URL = "https://entrez.enphaseenergy.com/login";
+ private static final String TOKEN_URL = "https://entrez.enphaseenergy.com/entrez_tokens";
+
+ private final Logger logger = LoggerFactory.getLogger(EntrezConnector.class);
+ private final Gson gson = new GsonBuilder().create();
+ private final HttpClient httpClient;
+
+ private static final long CONNECT_TIMEOUT_SECONDS = 10;
+
+ public EntrezConnector(final HttpClient httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ public String retrieveJwt(final String username, final String password, final String siteId, final String serialNum)
+ throws EntrezConnectionException {
+ final String session = login(username, password);
+ final Fields fields = new Fields();
+ fields.put("Site", siteId);
+ fields.put("serialNum", serialNum);
+
+ final URI uri = URI.create(TOKEN_URL);
+ logger.trace("Retrieving jwt from '{}'", uri);
+ final Request request = httpClient.newRequest(uri).method(HttpMethod.POST)
+ .cookie(new HttpCookie(SESSION_COOKIE_NAME, session)).content(new FormContentProvider(fields))
+ .timeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+
+ final ContentResponse response = send(request);
+ final String contentAsString = response.getContentAsString();
+ final Document document = Jsoup.parse(contentAsString);
+ final Elements elements = document.select(ELEMENT_ID_JWT_TOKEN);
+ final Element first = elements.first();
+
+ if (first == null) {
+ logger.debug("Could not select element '{}' in received data from entrez site. Received data: {}",
+ ELEMENT_ID_JWT_TOKEN, contentAsString);
+ throw new EntrezConnectionException("Could not parse data from entrez site");
+ }
+ return first.text();
+ }
+
+ public EntrezJwtDTO processJwt(final String jwt) throws EntrezConnectionException {
+ try {
+ final String[] parts = jwt.split("\\.", 0);
+ if (parts.length < 2) {
+ logger.debug("Could not split data into 2 parts. Recevied data: {}", jwt);
+ throw new EntrezConnectionException("Could not parse data from entrez site");
+ }
+ final EntrezJwtHeaderDTO header = gson.fromJson(
+ new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8),
+ EntrezJwtHeaderDTO.class);
+ final EntrezJwtBodyDTO body = gson.fromJson(
+ new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8),
+ EntrezJwtBodyDTO.class);
+
+ return new EntrezJwtDTO(header, body);
+ } catch (JsonSyntaxException | IllegalArgumentException e) {
+ throw new EntrezConnectionException("Could not parse data from entrez site:", e);
+ }
+ }
+
+ private String login(final String username, final String password) throws EntrezConnectionException {
+ final Fields fields = new Fields();
+ fields.put("username", username);
+ fields.put("password", password);
+
+ final URI uri = URI.create(LOGIN_URL);
+ logger.trace("Retrieving session id from '{}'", uri);
+ final Request request = httpClient.newRequest(uri).method(HttpMethod.POST)
+ .content(new FormContentProvider(fields)).timeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ final ContentResponse response = send(request);
+
+ if (response.getStatus() == 200 && response.getHeaders().contains(HttpHeader.SET_COOKIE)) {
+ final List cookies = HttpCookie.parse(response.getHeaders().get(HttpHeader.SET_COOKIE));
+
+ for (final HttpCookie c : cookies) {
+ if (SESSION_COOKIE_NAME.equals(c.getName())) {
+ return c.getValue();
+ }
+ }
+ }
+ logger.debug("Failed to login to Entrez portal. Portal returned status: {}. Response from Entrez portal: {}",
+ response.getStatus(), response.getContentAsString());
+ throw new EntrezConnectionException(
+ "Could not login to Entrez JWT Portal. Status code:" + response.getStatus());
+ }
+
+ private ContentResponse send(final Request request) throws EntrezConnectionException {
+ try {
+ return request.send();
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new EntrezConnectionException("Interrupted");
+ } catch (final TimeoutException e) {
+ logger.debug("TimeoutException: {}", e.getMessage());
+ throw new EntrezConnectionException("Connection timeout: ", e);
+ } catch (final ExecutionException e) {
+ logger.debug("ExecutionException: {}", e.getMessage(), e);
+ throw new EntrezConnectionException("Could not retrieve data: ", e.getCause());
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/handler/EnvoyBridgeHandler.java b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/handler/EnvoyBridgeHandler.java
index 4ebb1f6ec..e24474944 100644
--- a/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/handler/EnvoyBridgeHandler.java
+++ b/bundles/org.openhab.binding.enphase/src/main/java/org/openhab/binding/enphase/internal/handler/EnvoyBridgeHandler.java
@@ -18,10 +18,12 @@ import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.ENVOY
import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.ENVOY_WATT_HOURS_LIFETIME;
import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.ENVOY_WATT_HOURS_SEVEN_DAYS;
import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.ENVOY_WATT_HOURS_TODAY;
+import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.PROPERTY_VERSION;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
+import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
@@ -35,13 +37,16 @@ import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.enphase.internal.EnphaseBindingConstants;
import org.openhab.binding.enphase.internal.EnvoyConfiguration;
-import org.openhab.binding.enphase.internal.EnvoyConnectionException;
import org.openhab.binding.enphase.internal.EnvoyHostAddressCache;
-import org.openhab.binding.enphase.internal.EnvoyNoHostnameException;
import org.openhab.binding.enphase.internal.discovery.EnphaseDevicesDiscoveryService;
import org.openhab.binding.enphase.internal.dto.EnvoyEnergyDTO;
import org.openhab.binding.enphase.internal.dto.InventoryJsonDTO.DeviceDTO;
import org.openhab.binding.enphase.internal.dto.InverterDTO;
+import org.openhab.binding.enphase.internal.exception.EnphaseException;
+import org.openhab.binding.enphase.internal.exception.EntrezConnectionException;
+import org.openhab.binding.enphase.internal.exception.EntrezJwtInvalidException;
+import org.openhab.binding.enphase.internal.exception.EnvoyConnectionException;
+import org.openhab.binding.enphase.internal.exception.EnvoyNoHostnameException;
import org.openhab.core.cache.ExpiringCache;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.QuantityType;
@@ -55,6 +60,8 @@ import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.thing.binding.builder.BridgeBuilder;
+import org.openhab.core.thing.util.ThingHandlerHelper;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.UnDefType;
@@ -79,10 +86,11 @@ public class EnvoyBridgeHandler extends BaseBridgeHandler {
private static final long RETRY_RECONNECT_SECONDS = 10;
private final Logger logger = LoggerFactory.getLogger(EnvoyBridgeHandler.class);
- private final EnvoyConnector connector;
private final EnvoyHostAddressCache envoyHostnameCache;
+ private final EnvoyConnectorWrapper connectorWrapper;
private EnvoyConfiguration configuration = new EnvoyConfiguration();
+
private @Nullable ScheduledFuture> updataDataFuture;
private @Nullable ScheduledFuture> updateHostnameFuture;
private @Nullable ExpiringCache