Skip to content

Commit

Permalink
[enphase] Add Entrez/JWT support for newer software versions of Envoy (
Browse files Browse the repository at this point in the history
…openhab#15077)

Co-authored-by: Joe Inkenbrandt <joe@inkenbrandt.com>
Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>
Signed-off-by: querdenker2k <querdenker2k@gmx.de>
  • Loading branch information
2 people authored and querdenker2k committed Oct 29, 2023
1 parent defdbed commit 8141d99
Show file tree
Hide file tree
Showing 22 changed files with 1,326 additions and 196 deletions.
7 changes: 7 additions & 0 deletions bundles/org.openhab.binding.enphase/NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -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
33 changes: 31 additions & 2 deletions bundles/org.openhab.binding.enphase/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
13 changes: 13 additions & 0 deletions bundles/org.openhab.binding.enphase/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,17 @@

<name>openHAB Add-ons :: Bundles :: Enphase Binding</name>

<properties>
<jsoup.version>1.15.3</jsoup.version>
</properties>

<dependencies>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>${jsoup.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

<feature name="openhab-binding-enphase" description="Enphase Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle dependency="true">mvn:org.jsoup/jsoup/1.15.3</bundle>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.enphase/${project.version}</bundle>
</feature>
</features>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -33,11 +35,11 @@
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
*/
Expand All @@ -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
Expand All @@ -71,7 +90,7 @@ public boolean supportsThingType(final ThingTypeUID thingTypeUID) {
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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,6 +23,7 @@

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;
Expand Down Expand Up @@ -124,7 +127,7 @@ private void scanForDeviceThings(final EnvoyBridgeHandler envoyHandler, final Th

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<String, Object> properties = new HashMap<>(1);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,8 +43,7 @@
/**
* 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
Expand All @@ -55,7 +59,7 @@ public class EnvoyDiscoveryParticipant implements MDNSDiscoveryParticipant, Envo

@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return Set.of(EnphaseBindingConstants.THING_TYPE_ENPHASE_ENVOY);
return Set.of(THING_TYPE_ENPHASE_ENVOY);
}

@Override
Expand Down Expand Up @@ -101,7 +105,7 @@ public String getServiceType() {
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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 8141d99

Please sign in to comment.