Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[enphase] Add Entrez/JWT support for newer software versions of Envoy #15077

Merged
merged 5 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
jlaur marked this conversation as resolved.
Show resolved Hide resolved
</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