mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[enphase] Initial contribution (#9883)
Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>
This commit is contained in:
parent
4f018b8aec
commit
53d168991c
@ -69,6 +69,7 @@
|
||||
/bundles/org.openhab.binding.energenie/ @hmerk
|
||||
/bundles/org.openhab.binding.enigma2/ @gdolfen
|
||||
/bundles/org.openhab.binding.enocean/ @fruggy83
|
||||
/bundles/org.openhab.binding.enphase/ @Hilbrand
|
||||
/bundles/org.openhab.binding.enturno/ @klocsson
|
||||
/bundles/org.openhab.binding.epsonprojector/ @mlobstein
|
||||
/bundles/org.openhab.binding.etherrain/ @dfad1469
|
||||
|
@ -331,6 +331,11 @@
|
||||
<artifactId>org.openhab.binding.enocean</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.enphase</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.enturno</artifactId>
|
||||
|
13
bundles/org.openhab.binding.enphase/NOTICE
Normal file
13
bundles/org.openhab.binding.enphase/NOTICE
Normal file
@ -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
|
113
bundles/org.openhab.binding.enphase/README.md
Normal file
113
bundles/org.openhab.binding.enphase/README.md
Normal file
@ -0,0 +1,113 @@
|
||||
# Enphase Binding
|
||||
|
||||
This is the binding for the [Enphase](https://enphase.com/) Envoy Solar Panel gateway.
|
||||
The binding uses the local API of the Envoy gateway.
|
||||
Some calls can be made without authentication and some use a user name and password.
|
||||
The default user name is `envoy` and the default password is the last 6 numbers of the serial number.
|
||||
The Envoy gateway updates the data every 5 minutes.
|
||||
Therefore using a refresh rate shorter doesn't provide more information.
|
||||
|
||||
## Supported Things
|
||||
|
||||
The follow things are supported:
|
||||
|
||||
* `envoy` The Envoy gateway thing, which is a bridge thing.
|
||||
* `inverter` A Enphase micro inverter connected to a solar panel.
|
||||
* `relay` A Enphase relay.
|
||||
|
||||
Not all Envoy gateways support all channels and things.
|
||||
Therefore some data on inverters and the relay may not be available.
|
||||
The binding auto detects which data is available and will report this in the log on initialization of the gateway bridge.
|
||||
|
||||
## Discovery
|
||||
|
||||
The binding can discover Envoy gateways, micro inverters and relays.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The Envoy gateway thing `envoy` has the following configuration options:
|
||||
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
The micro inverter `inverter` and `relay` things have only 1 parameter:
|
||||
|
||||
| parameter | required | description |
|
||||
|--------------|----------|-----------------------------------|
|
||||
| serialNumber | yes | The serial number of the inverter |
|
||||
|
||||
## Channels
|
||||
|
||||
The `envoy` thing has can show both production as well as consumption data.
|
||||
There are channel groups for `production` and `consumption` data.
|
||||
The `consumption` data is only available if the gateway reports this.
|
||||
A example of a production channel name is: `production#wattsNow`.
|
||||
|
||||
| channel | type | description |
|
||||
|--------------------|---------------|---------------------------------------|
|
||||
| wattHoursToday | Number:Energy | Watt hours produced today |
|
||||
| wattHoursSevenDays | Number:Energy | Watt hours produced the last 7 days |
|
||||
| wattHoursLifetime | Number:Energy | Watt hours produced over the lifetime |
|
||||
| wattsNow | Number:Power | Latest watts produced |
|
||||
|
||||
The `inverter` thing has the following channels:
|
||||
|
||||
| channel | type | description |
|
||||
|-----------------|--------------|--------------------------------------|
|
||||
| lastReportWatts | Number:Power | Last reported power delivery |
|
||||
| maxReportWatts | Number:Power | Maximum reported power |
|
||||
| lastReportDate | DateTime | Date of last reported power delivery |
|
||||
|
||||
The following channels are only available if supported by the Envoy gateway:
|
||||
|
||||
The `relay` thing has the following channels:
|
||||
|
||||
| channel | type | description |
|
||||
|-----------------|--------------|--------------------------------------------------------|
|
||||
| relay | Contact | Status of the relay. |
|
||||
| line1Connected | Contact | If power line 1 is connected. If closed it's connected |
|
||||
| line2Connected | Contact | If power line 2 is connected. If closed it's connected |
|
||||
| line2Connected | Contact | If power line 3 is connected. If closed it's connected |
|
||||
|
||||
The `inverter` and `relay` have the following additional advanced channels:
|
||||
|
||||
| channel | type | description |
|
||||
|-----------------|--------------------|--------------------------------------|
|
||||
| producing | Switch (Read Only) | If the device is producing |
|
||||
| communicating | Switch (Read Only) | If the device is communicating |
|
||||
| provisioned | Switch (Read Only) | If the device is provisioned |
|
||||
| operating | Switch (Read Only) | If the device is operating |
|
||||
|
||||
## Full Example
|
||||
|
||||
Things example:
|
||||
|
||||
```
|
||||
Bridge enphase:envoy:789012 "Envoy" [ serialNumber="12345789012" ] {
|
||||
Things:
|
||||
inverter 123456 "Enphase Inverter 123456" [ serialNumber="789012123456" ]
|
||||
inverter 223456 "Enphase Inverter 223456" [ serialNumber="789012223456" ]
|
||||
}
|
||||
```
|
||||
|
||||
Items example:
|
||||
|
||||
```
|
||||
Number:Power envoyWattsNow "Watts Now [%d %unit%]" { channel="enphase:envoy:789012:production#wattsNow" }
|
||||
Number:Energy envoyWattHoursToday "Watt Hours Today [%d %unit%]" { channel="enphase:envoy:789012:production#wattHoursToday" }
|
||||
Number:Energy envoyWattHours7Days "Watt Hours 7 Days [%.1f kWh]" { channel="enphase:envoy:789012:production#wattHoursSevenDays" }
|
||||
Number:Energy envoyWattHoursLifetime "Watt Hours Lifetime [%.1f kWh]" { channel="enphase:envoy:789012:production#wattHoursLifetime" }
|
||||
|
||||
Number:Power i1LastReportWatts "Last Report [%d %unit%]" { channel="enphase:inverter:789012:123456:lastReportWatts" }
|
||||
Number:Power i1MaxReportWatts "Max Report [%d %unit%]" { channel="enphase:inverter:789012:123456:maxReportWatts" }
|
||||
DateTime i1LastReportDate "Last Report Date [%1$tY-%1$tm-%1$td %1$tH:%1$tM]" { channel="enphase:inverter:789012:123456:lastReportDate" }
|
||||
|
||||
Number:Power i2LastReportWatts "Last Report [%d %unit%]" { channel="enphase:inverter:789012:223456:lastReportWatts" }
|
||||
Number:Power i21MaxReportWatts "Max Report [%d %unit%]" { channel="enphase:inverter:789012:223456:maxReportWatts" }
|
||||
DateTime i2LastReportDate "Last Report Date [%1$tY-%1$tm-%1$td %1$tH:%1$tM]" { channel="enphase:inverter:789012:223456:lastReportDate" }
|
||||
```
|
17
bundles/org.openhab.binding.enphase/pom.xml
Normal file
17
bundles/org.openhab.binding.enphase/pom.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.enphase</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Enphase Binding</name>
|
||||
|
||||
</project>
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.enphase-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
|
||||
</repository>
|
||||
|
||||
<feature name="openhab-binding-enphase" description="Enphase Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.enphase/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link EnphaseBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EnphaseBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "enphase";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_ENPHASE_ENVOY = new ThingTypeUID(BINDING_ID, "envoy");
|
||||
public static final ThingTypeUID THING_TYPE_ENPHASE_INVERTER = new ThingTypeUID(BINDING_ID, "inverter");
|
||||
public static final ThingTypeUID THING_TYPE_ENPHASE_RELAY = new ThingTypeUID(BINDING_ID, "relay");
|
||||
|
||||
// Configuration parameters
|
||||
public static final String CONFIG_SERIAL_NUMBER = "serialNumber";
|
||||
public static final String CONFIG_HOSTNAME = "hostname";
|
||||
public static final String CONFIG_USERNAME = "username";
|
||||
public static final String CONFIG_PASSWORD = "password";
|
||||
public static final String CONFIG_REFRESH = "refresh";
|
||||
public static final String PROPERTY_VERSION = "version";
|
||||
|
||||
// Envoy gateway channels
|
||||
public static final String ENVOY_CHANNELGROUP_CONSUMPTION = "consumption";
|
||||
public static final String ENVOY_WATT_HOURS_TODAY = "wattHoursToday";
|
||||
public static final String ENVOY_WATT_HOURS_SEVEN_DAYS = "wattHoursSevenDays";
|
||||
public static final String ENVOY_WATT_HOURS_LIFETIME = "wattHoursLifetime";
|
||||
public static final String ENVOY_WATTS_NOW = "wattsNow";
|
||||
|
||||
// Device channels
|
||||
public static final String DEVICE_CHANNEL_STATUS = "status";
|
||||
public static final String DEVICE_CHANNEL_PRODUCING = "producing";
|
||||
public static final String DEVICE_CHANNEL_COMMUNICATING = "communicating";
|
||||
public static final String DEVICE_CHANNEL_PROVISIONED = "provisioned";
|
||||
public static final String DEVICE_CHANNEL_OPERATING = "operating";
|
||||
|
||||
// Inverter channels
|
||||
public static final String INVERTER_CHANNEL_LAST_REPORT_WATTS = "lastReportWatts";
|
||||
public static final String INVERTER_CHANNEL_MAX_REPORT_WATTS = "maxReportWatts";
|
||||
public static final String INVERTER_CHANNEL_LAST_REPORT_DATE = "lastReportDate";
|
||||
|
||||
// Relay channels
|
||||
public static final String RELAY_CHANNEL_RELAY = "relay";
|
||||
public static final String RELAY_CHANNEL_LINE_1_CONNECTED = "line1Connected";
|
||||
public static final String RELAY_CHANNEL_LINE_2_CONNECTED = "line2Connected";
|
||||
public static final String RELAY_CHANNEL_LINE_3_CONNECTED = "line3Connected";
|
||||
|
||||
public static final String RELAY_STATUS_CLOSED = "closed";
|
||||
|
||||
// Properties
|
||||
public static final String DEVICE_PROPERTY_PART_NUMBER = "partNumber";
|
||||
|
||||
// Discovery constants
|
||||
public static final String DISCOVERY_SERIAL = "serialnum";
|
||||
public static final String DISCOVERY_VERSION = "protovers";
|
||||
|
||||
// Status messages
|
||||
public static final String DEVICE_STATUS_OK = "envoy.global.ok";
|
||||
public static final String ERROR_NODATA = "error.nodata";
|
||||
|
||||
public enum EnphaseDeviceType {
|
||||
ACB, // AC Battery
|
||||
PSU, // Inverter
|
||||
NSRB; // Network system relay controller
|
||||
|
||||
public static @Nullable EnphaseDeviceType safeValueOf(final String type) {
|
||||
try {
|
||||
return valueOf(type);
|
||||
} catch (final IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the default password from the serial number.
|
||||
*
|
||||
* @param serialNumber serial number to use
|
||||
* @return the default password or empty string if serial number is to short.
|
||||
*/
|
||||
public static String defaultPassword(final String serialNumber) {
|
||||
return isValidSerial(serialNumber) ? serialNumber.substring(serialNumber.length() - 6) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the serial number is at least long enough to contain the default password.
|
||||
*
|
||||
* @param serialNumber serial number to check
|
||||
* @return true if not null and at least 6 characters long.
|
||||
*/
|
||||
public static boolean isValidSerial(@Nullable final String serialNumber) {
|
||||
return serialNumber != null && serialNumber.length() > 6;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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;
|
||||
|
||||
import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
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;
|
||||
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.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link EnphaseHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.enphase", service = ThingHandlerFactory.class)
|
||||
public class EnphaseHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ENPHASE_ENVOY,
|
||||
THING_TYPE_ENPHASE_INVERTER, THING_TYPE_ENPHASE_RELAY);
|
||||
|
||||
private final MessageTranslator messageTranslator;
|
||||
private final HttpClient commonHttpClient;
|
||||
private final EnvoyHostAddressCache envoyHostAddressCache;
|
||||
|
||||
@Activate
|
||||
public EnphaseHandlerFactory(final @Reference LocaleProvider localeProvider,
|
||||
final @Reference TranslationProvider i18nProvider, final @Reference HttpClientFactory httpClientFactory,
|
||||
@Reference final EnvoyHostAddressCache envoyHostAddressCache) {
|
||||
messageTranslator = new MessageTranslator(localeProvider, i18nProvider);
|
||||
commonHttpClient = httpClientFactory.getCommonHttpClient();
|
||||
this.envoyHostAddressCache = envoyHostAddressCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(final ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(final Thing thing) {
|
||||
final ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (THING_TYPE_ENPHASE_ENVOY.equals(thingTypeUID)) {
|
||||
return new EnvoyBridgeHandler((Bridge) thing, commonHttpClient, envoyHostAddressCache);
|
||||
} else if (THING_TYPE_ENPHASE_INVERTER.equals(thingTypeUID)) {
|
||||
return new EnphaseInverterHandler(thing, messageTranslator);
|
||||
} else if (THING_TYPE_ENPHASE_RELAY.equals(thingTypeUID)) {
|
||||
return new EnphaseRelayHandler(thing, messageTranslator);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link EnvoyConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EnvoyConfiguration {
|
||||
|
||||
public static final String DEFAULT_USERNAME = "envoy";
|
||||
private static final int DEFAULT_REFRESH_MINUTES = 5;
|
||||
|
||||
public String serialNumber = "";
|
||||
public String hostname = "";
|
||||
public String username = DEFAULT_USERNAME;
|
||||
public String password = "";
|
||||
public int refresh = DEFAULT_REFRESH_MINUTES;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EnvoyConfiguration [serialNumber=" + serialNumber + ", hostname=" + hostname + ", username=" + username
|
||||
+ ", password=" + password + ", refresh=" + refresh + "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Exception thrown when a connection problem occurs to the Envoy gateway.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EnvoyConnectionException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public EnvoyConnectionException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public EnvoyConnectionException(final String message, final @Nullable Throwable e) {
|
||||
super(message + (e == null ? "" : e.getMessage()), e);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Service that keeps track of host names/ip addresses of discovered Envoy devices.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface EnvoyHostAddressCache {
|
||||
|
||||
/**
|
||||
* Returns the known host name/ip address for the device with the given serial number.
|
||||
* If not known an empty string is returned.
|
||||
*
|
||||
* @param serialNumber serial number of device to get host address for
|
||||
* @return the known host address or an empty string if not known
|
||||
*/
|
||||
String getLastKnownHostAddress(String serialNumber);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception thrown when a api call is made while the hostname / ip address is not set.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EnvoyNoHostnameException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public EnvoyNoHostnameException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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;
|
||||
|
||||
import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.ERROR_NODATA;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.osgi.framework.Bundle;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
|
||||
/**
|
||||
* Class to get the message for the enphase message code.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class MessageTranslator {
|
||||
|
||||
private final LocaleProvider localeProvider;
|
||||
private final TranslationProvider i18nProvider;
|
||||
private final Bundle bundle;
|
||||
|
||||
public MessageTranslator(LocaleProvider localeProvider, TranslationProvider i18nProvider) {
|
||||
this.localeProvider = localeProvider;
|
||||
this.i18nProvider = i18nProvider;
|
||||
bundle = FrameworkUtil.getBundle(this.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message text for the enphase message code.
|
||||
*
|
||||
* @param key the enphase message code
|
||||
* @return translated key
|
||||
*/
|
||||
public @Nullable String translate(String key) {
|
||||
return i18nProvider.getText(bundle, key, ERROR_NODATA, localeProvider.getLocale());
|
||||
}
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.discovery;
|
||||
|
||||
import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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;
|
||||
import org.openhab.binding.enphase.internal.handler.EnvoyBridgeHandler;
|
||||
import org.openhab.core.config.discovery.AbstractDiscoveryService;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.DiscoveryService;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Discovery service to discovery Enphase inverters connected to an Envoy gateway.
|
||||
*
|
||||
* @author Thomas Hentschel - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EnphaseDevicesDiscoveryService extends AbstractDiscoveryService
|
||||
implements ThingHandlerService, DiscoveryService {
|
||||
|
||||
private static final int TIMEOUT_SECONDS = 20;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EnphaseDevicesDiscoveryService.class);
|
||||
private @Nullable EnvoyBridgeHandler envoyHandler;
|
||||
|
||||
public EnphaseDevicesDiscoveryService() {
|
||||
super(Collections.singleton(THING_TYPE_ENPHASE_INVERTER), TIMEOUT_SECONDS, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(final @Nullable ThingHandler handler) {
|
||||
if (handler instanceof EnvoyBridgeHandler) {
|
||||
envoyHandler = (EnvoyBridgeHandler) handler;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return envoyHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
removeOlderResults(getTimestampOfLastScan());
|
||||
final EnvoyBridgeHandler envoyHandler = this.envoyHandler;
|
||||
|
||||
if (envoyHandler == null || !envoyHandler.isOnline()) {
|
||||
logger.debug("Envoy handler not available or online: {}", envoyHandler);
|
||||
return;
|
||||
}
|
||||
final ThingUID uid = envoyHandler.getThing().getUID();
|
||||
|
||||
scanForInverterThings(envoyHandler, uid);
|
||||
scanForDeviceThings(envoyHandler, uid);
|
||||
}
|
||||
|
||||
private void scanForInverterThings(final EnvoyBridgeHandler envoyHandler, final ThingUID bridgeID) {
|
||||
final Map<String, @Nullable InverterDTO> inverters = envoyHandler.getInvertersData(true);
|
||||
|
||||
if (inverters == null) {
|
||||
logger.debug("No inverter data for Enphase inverters in discovery for Envoy {}.", bridgeID);
|
||||
} else {
|
||||
for (final Entry<String, @Nullable InverterDTO> entry : inverters.entrySet()) {
|
||||
discover(bridgeID, entry.getKey(), THING_TYPE_ENPHASE_INVERTER, "Inverter ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans for other device things ('other' as in: no inverters).
|
||||
*
|
||||
* @param envoyHandler
|
||||
* @param bridgeID
|
||||
*/
|
||||
private void scanForDeviceThings(final EnvoyBridgeHandler envoyHandler, final ThingUID bridgeID) {
|
||||
final Map<String, @Nullable DeviceDTO> devices = envoyHandler.getDevices(true);
|
||||
|
||||
if (devices == null) {
|
||||
logger.debug("No device data for Enphase devices in discovery for Envoy {}.", bridgeID);
|
||||
} else {
|
||||
for (final Entry<String, @Nullable DeviceDTO> entry : devices.entrySet()) {
|
||||
final DeviceDTO dto = entry.getValue();
|
||||
final EnphaseDeviceType type = dto == null ? null : EnphaseDeviceType.safeValueOf(dto.type);
|
||||
|
||||
if (type == EnphaseDeviceType.NSRB) {
|
||||
discover(bridgeID, entry.getKey(), THING_TYPE_ENPHASE_RELAY, "Relay ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void discover(final ThingUID bridgeID, final String serialNumber, final ThingTypeUID typeUID,
|
||||
final String label) {
|
||||
final String shortSerialNumber = defaultPassword(serialNumber);
|
||||
final ThingUID thingUID = new ThingUID(typeUID, bridgeID, shortSerialNumber);
|
||||
final Map<String, Object> properties = new HashMap<>(1);
|
||||
|
||||
properties.put(CONFIG_SERIAL_NUMBER, serialNumber);
|
||||
final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeID)
|
||||
.withRepresentationProperty(CONFIG_SERIAL_NUMBER).withProperties(properties)
|
||||
.withLabel("Enphase " + label + shortSerialNumber).build();
|
||||
thingDiscovered(discoveryResult);
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.discovery;
|
||||
|
||||
import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.*;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
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.EnvoyHostAddressCache;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.slf4j.Logger;
|
||||
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.
|
||||
*
|
||||
* @author Thomas Hentschel - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@Component(service = { EnvoyHostAddressCache.class, MDNSDiscoveryParticipant.class })
|
||||
@NonNullByDefault
|
||||
public class EnvoyDiscoveryParticipant implements MDNSDiscoveryParticipant, EnvoyHostAddressCache {
|
||||
private static final String ENVOY_MDNS_ID = "envoy";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EnvoyDiscoveryParticipant.class);
|
||||
|
||||
private final Map<String, @Nullable String> lastKnownHostAddresses = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return Collections.singleton(EnphaseBindingConstants.THING_TYPE_ENPHASE_ENVOY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return "_enphase-envoy._tcp.local.";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscoveryResult createResult(final ServiceInfo info) {
|
||||
final String id = info.getName();
|
||||
|
||||
logger.debug("id found: {} with type: {}", id, info.getType());
|
||||
|
||||
if (!id.contains(ENVOY_MDNS_ID)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (info.getInet4Addresses().length == 0 || info.getInet4Addresses()[0] == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final ThingUID uid = getThingUID(info);
|
||||
|
||||
if (uid == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Inet4Address hostname = info.getInet4Addresses()[0];
|
||||
final String serialNumber = info.getPropertyString(DISCOVERY_SERIAL);
|
||||
|
||||
if (serialNumber == null) {
|
||||
logger.debug("No serial number found in data for discovered Envoy {}: {}", id, info);
|
||||
return null;
|
||||
}
|
||||
final String version = info.getPropertyString(DISCOVERY_VERSION);
|
||||
final String hostAddress = hostname == null ? "" : hostname.getHostAddress();
|
||||
|
||||
lastKnownHostAddresses.put(serialNumber, hostAddress);
|
||||
final Map<String, Object> properties = new HashMap<>(3);
|
||||
|
||||
properties.put(CONFIG_SERIAL_NUMBER, serialNumber);
|
||||
properties.put(CONFIG_HOSTNAME, hostAddress);
|
||||
properties.put(PROPERTY_VERSION, version);
|
||||
return DiscoveryResultBuilder.create(uid).withProperties(properties)
|
||||
.withRepresentationProperty(CONFIG_SERIAL_NUMBER)
|
||||
.withLabel("Enphase Envoy " + defaultPassword(serialNumber)).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastKnownHostAddress(final String serialNumber) {
|
||||
final String hostAddress = lastKnownHostAddresses.get(serialNumber);
|
||||
|
||||
return hostAddress == null ? "" : hostAddress;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(final ServiceInfo info) {
|
||||
final String name = info.getName();
|
||||
|
||||
if (!name.contains(ENVOY_MDNS_ID)) {
|
||||
logger.trace("Found other type of device that is not recognized as an Envoy: {}", name);
|
||||
return null;
|
||||
}
|
||||
if (info.getInet4Addresses().length == 0 || info.getInet4Addresses()[0] == null) {
|
||||
logger.debug("Found an Envoy, but no ip address is given: {}", info);
|
||||
return null;
|
||||
}
|
||||
logger.debug("ServiceInfo addr: {}", info.getInet4Addresses()[0]);
|
||||
if (getServiceType().equals(info.getType())) {
|
||||
final String serial = info.getPropertyString(DISCOVERY_SERIAL);
|
||||
|
||||
logger.debug("Discovered an Envoy with serial number '{}'", serial);
|
||||
return new ThingUID(THING_TYPE_ENPHASE_ENVOY, serial);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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 from api/v1/production api call.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
public class EnvoyEnergyDTO {
|
||||
public int wattHoursToday;
|
||||
public int wattHoursSevenDays;
|
||||
public int wattHoursLifetime;
|
||||
public int wattsNow;
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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 handling errors returned by the Envoy gateway.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
public class EnvoyErrorDTO {
|
||||
public int status;
|
||||
public String error;
|
||||
public String info;
|
||||
public String moreInfo;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EnvoyErrorDTO [status=" + status + ", error=" + error + ", info=" + info + ", moreInfo=" + moreInfo
|
||||
+ "]";
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
public class InventoryJsonDTO {
|
||||
|
||||
public class DeviceDTO {
|
||||
public String type;
|
||||
|
||||
@SerializedName("part_num")
|
||||
public String partNumber;
|
||||
@SerializedName("serial_num")
|
||||
public String serialNumber;
|
||||
|
||||
@SerializedName("device_status")
|
||||
private String[] deviceStatus;
|
||||
@SerializedName("last_rpt_date")
|
||||
public String lastReportDate;
|
||||
public boolean producing;
|
||||
public boolean communicating;
|
||||
public boolean provisioned;
|
||||
public boolean operating;
|
||||
// NSRB data
|
||||
public String relay;
|
||||
@SerializedName("line1-connected")
|
||||
public boolean line1Connected;
|
||||
@SerializedName("line2-connected")
|
||||
public boolean line2Connected;
|
||||
@SerializedName("line3-connected")
|
||||
public boolean line3Connected;
|
||||
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
public String getDeviceStatus() {
|
||||
return deviceStatus == null || deviceStatus.length == 0 ? "" : deviceStatus[0];
|
||||
}
|
||||
}
|
||||
|
||||
public String type;
|
||||
public DeviceDTO[] devices;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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 Inverter data.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
public class InverterDTO {
|
||||
public String serialNumber;
|
||||
public long lastReportDate;
|
||||
public int devType;
|
||||
public int lastReportWatts;
|
||||
public int maxReportWatts;
|
||||
|
||||
/**
|
||||
* @return the serialNumber
|
||||
*/
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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 Envoy production and consumption data from production.json api call.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
public class ProductionJsonDTO {
|
||||
|
||||
public static class DataDTO {
|
||||
public String type;
|
||||
public int activeCount;
|
||||
public float whLifetime;
|
||||
public float whLastSevenDays;
|
||||
public float whToday;
|
||||
public float wNow;
|
||||
public float rmsCurrent;
|
||||
public float rmsVoltage;
|
||||
public float reactPwr;
|
||||
public float apprntPwr;
|
||||
public float pwrFactor;
|
||||
public long readingTime;
|
||||
public float varhLeadToday;
|
||||
public float varhLagToday;
|
||||
public float vahToday;
|
||||
public float varhLeadLifetime;
|
||||
public float varhLagLifetime;
|
||||
public float vahLifetime;
|
||||
}
|
||||
|
||||
public DataDTO[] production;
|
||||
public DataDTO[] consumption;
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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 static org.openhab.binding.enphase.internal.EnphaseBindingConstants.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
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.MessageTranslator;
|
||||
import org.openhab.binding.enphase.internal.dto.InventoryJsonDTO.DeviceDTO;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
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.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Generic base Thing handler for different Enphase devices.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
abstract class EnphaseDeviceHandler extends BaseThingHandler {
|
||||
protected final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
protected @Nullable DeviceDTO lastKnownDeviceState;
|
||||
|
||||
private final MessageTranslator messageTranslator;
|
||||
private String serialNumber = "";
|
||||
|
||||
public EnphaseDeviceHandler(final Thing thing, MessageTranslator messageTranslator) {
|
||||
super(thing);
|
||||
this.messageTranslator = messageTranslator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the serialNumber
|
||||
*/
|
||||
public String getSerialNumber() {
|
||||
return serialNumber;
|
||||
}
|
||||
|
||||
protected void handleCommandRefresh(final String channelId) {
|
||||
switch (channelId) {
|
||||
case DEVICE_CHANNEL_STATUS:
|
||||
refreshStatus(lastKnownDeviceState);
|
||||
break;
|
||||
case DEVICE_CHANNEL_PRODUCING:
|
||||
refreshProducing(lastKnownDeviceState);
|
||||
break;
|
||||
case DEVICE_CHANNEL_COMMUNICATING:
|
||||
refreshCommunicating(lastKnownDeviceState);
|
||||
break;
|
||||
case DEVICE_CHANNEL_PROVISIONED:
|
||||
refreshProvisioned(lastKnownDeviceState);
|
||||
break;
|
||||
case DEVICE_CHANNEL_OPERATING:
|
||||
refreshOperating(lastKnownDeviceState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshStatus(final @Nullable DeviceDTO deviceDTO) {
|
||||
updateState(DEVICE_CHANNEL_STATUS, deviceDTO == null ? UnDefType.UNDEF
|
||||
: new StringType(messageTranslator.translate((deviceDTO.getDeviceStatus()))));
|
||||
}
|
||||
|
||||
private void refreshProducing(final @Nullable DeviceDTO deviceDTO) {
|
||||
updateState(DEVICE_CHANNEL_PRODUCING,
|
||||
deviceDTO == null ? UnDefType.UNDEF : OnOffType.from(deviceDTO.producing));
|
||||
}
|
||||
|
||||
private void refreshCommunicating(final @Nullable DeviceDTO deviceDTO) {
|
||||
updateState(DEVICE_CHANNEL_COMMUNICATING,
|
||||
deviceDTO == null ? UnDefType.UNDEF : OnOffType.from(deviceDTO.communicating));
|
||||
}
|
||||
|
||||
private void refreshProvisioned(final @Nullable DeviceDTO deviceDTO) {
|
||||
updateState(DEVICE_CHANNEL_PROVISIONED,
|
||||
deviceDTO == null ? UnDefType.UNDEF : OnOffType.from(deviceDTO.provisioned));
|
||||
}
|
||||
|
||||
private void refreshOperating(final @Nullable DeviceDTO deviceDTO) {
|
||||
updateState(DEVICE_CHANNEL_OPERATING,
|
||||
deviceDTO == null ? UnDefType.UNDEF : OnOffType.from(deviceDTO.operating));
|
||||
}
|
||||
|
||||
public void refreshDeviceState(final @Nullable DeviceDTO deviceDTO) {
|
||||
refreshStatus(deviceDTO);
|
||||
refreshProducing(deviceDTO);
|
||||
refreshCommunicating(deviceDTO);
|
||||
refreshProvisioned(deviceDTO);
|
||||
refreshOperating(deviceDTO);
|
||||
refreshProperties(deviceDTO);
|
||||
refreshDeviceStatus(deviceDTO != null);
|
||||
}
|
||||
|
||||
public void refreshDeviceStatus(final boolean hasData) {
|
||||
if (isInitialized()) {
|
||||
if (hasData) {
|
||||
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
messageTranslator.translate(ERROR_NODATA));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshProperties(@Nullable final DeviceDTO deviceDTO) {
|
||||
if (deviceDTO != null) {
|
||||
final Map<String, String> properties = editProperties();
|
||||
|
||||
properties.put(DEVICE_PROPERTY_PART_NUMBER, deviceDTO.partNumber);
|
||||
updateProperties(properties);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
serialNumber = (String) getConfig().get(EnphaseBindingConstants.CONFIG_SERIAL_NUMBER);
|
||||
if (!EnphaseBindingConstants.isValidSerial(serialNumber)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial Number is not valid");
|
||||
} else {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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 static org.openhab.binding.enphase.internal.EnphaseBindingConstants.*;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.enphase.internal.MessageTranslator;
|
||||
import org.openhab.binding.enphase.internal.dto.InverterDTO;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link EnphaseInverterHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EnphaseInverterHandler extends EnphaseDeviceHandler {
|
||||
|
||||
private @Nullable InverterDTO lastKnownState;
|
||||
|
||||
public EnphaseInverterHandler(final Thing thing, MessageTranslator messageTranslator) {
|
||||
super(thing, messageTranslator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
final String channelId = channelUID.getId();
|
||||
|
||||
switch (channelId) {
|
||||
case INVERTER_CHANNEL_LAST_REPORT_WATTS:
|
||||
refreshLastReportWatts(lastKnownState);
|
||||
break;
|
||||
case INVERTER_CHANNEL_MAX_REPORT_WATTS:
|
||||
refreshMaxReportWatts(lastKnownState);
|
||||
break;
|
||||
case INVERTER_CHANNEL_LAST_REPORT_DATE:
|
||||
refreshLastReportDate(lastKnownState);
|
||||
break;
|
||||
default:
|
||||
super.handleCommandRefresh(channelId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void refreshInverterChannels(final @Nullable InverterDTO inverterDTO) {
|
||||
refreshLastReportWatts(inverterDTO);
|
||||
refreshMaxReportWatts(inverterDTO);
|
||||
refreshLastReportDate(inverterDTO);
|
||||
lastKnownState = inverterDTO;
|
||||
}
|
||||
|
||||
private void refreshLastReportWatts(final @Nullable InverterDTO inverterDTO) {
|
||||
updateState(INVERTER_CHANNEL_LAST_REPORT_WATTS,
|
||||
inverterDTO == null ? UnDefType.UNDEF : new QuantityType<>(inverterDTO.lastReportWatts, Units.WATT));
|
||||
}
|
||||
|
||||
private void refreshMaxReportWatts(final @Nullable InverterDTO inverterDTO) {
|
||||
updateState(INVERTER_CHANNEL_MAX_REPORT_WATTS,
|
||||
inverterDTO == null ? UnDefType.UNDEF : new QuantityType<>(inverterDTO.maxReportWatts, Units.WATT));
|
||||
}
|
||||
|
||||
private void refreshLastReportDate(final @Nullable InverterDTO inverterDTO) {
|
||||
final State state;
|
||||
|
||||
if (inverterDTO == null) {
|
||||
state = UnDefType.UNDEF;
|
||||
} else {
|
||||
final Instant instant = Instant.ofEpochSecond(inverterDTO.lastReportDate);
|
||||
final ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
|
||||
logger.trace("[{}] Epoch time {}, zonedDateTime: {}", getThing().getUID(), inverterDTO.lastReportDate,
|
||||
zonedDateTime);
|
||||
state = new DateTimeType(zonedDateTime);
|
||||
}
|
||||
updateState(INVERTER_CHANNEL_LAST_REPORT_DATE, state);
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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 static org.openhab.binding.enphase.internal.EnphaseBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.enphase.internal.MessageTranslator;
|
||||
import org.openhab.binding.enphase.internal.dto.InventoryJsonDTO.DeviceDTO;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
/**
|
||||
* The {@link EnphaseInverterHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EnphaseRelayHandler extends EnphaseDeviceHandler {
|
||||
|
||||
public EnphaseRelayHandler(final Thing thing, MessageTranslator messageTranslator) {
|
||||
super(thing, messageTranslator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
final String channelId = channelUID.getId();
|
||||
|
||||
switch (channelId) {
|
||||
case RELAY_CHANNEL_RELAY:
|
||||
refreshRelayChannel(lastKnownDeviceState);
|
||||
break;
|
||||
case RELAY_CHANNEL_LINE_1_CONNECTED:
|
||||
refreshLine1Connect(lastKnownDeviceState);
|
||||
break;
|
||||
case RELAY_CHANNEL_LINE_2_CONNECTED:
|
||||
refreshLine2Connect(lastKnownDeviceState);
|
||||
break;
|
||||
case RELAY_CHANNEL_LINE_3_CONNECTED:
|
||||
refreshLine3Connect(lastKnownDeviceState);
|
||||
break;
|
||||
default:
|
||||
super.handleCommandRefresh(channelId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshRelayChannel(@Nullable final DeviceDTO deviceDTO) {
|
||||
updateState(RELAY_CHANNEL_RELAY, deviceDTO == null ? UnDefType.UNDEF
|
||||
: (RELAY_STATUS_CLOSED.equals(deviceDTO.relay) ? OpenClosedType.CLOSED : OpenClosedType.OPEN));
|
||||
}
|
||||
|
||||
private void refreshLine1Connect(@Nullable final DeviceDTO deviceDTO) {
|
||||
updateState(RELAY_CHANNEL_LINE_1_CONNECTED, deviceDTO == null ? UnDefType.UNDEF
|
||||
: (deviceDTO.line1Connected ? OpenClosedType.CLOSED : OpenClosedType.OPEN));
|
||||
}
|
||||
|
||||
private void refreshLine2Connect(@Nullable final DeviceDTO deviceDTO) {
|
||||
updateState(RELAY_CHANNEL_LINE_2_CONNECTED, deviceDTO == null ? UnDefType.UNDEF
|
||||
: (deviceDTO.line2Connected ? OpenClosedType.CLOSED : OpenClosedType.OPEN));
|
||||
}
|
||||
|
||||
private void refreshLine3Connect(@Nullable final DeviceDTO deviceDTO) {
|
||||
updateState(RELAY_CHANNEL_LINE_3_CONNECTED, deviceDTO == null ? UnDefType.UNDEF
|
||||
: (deviceDTO.line3Connected ? OpenClosedType.CLOSED : OpenClosedType.OPEN));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshDeviceState(@Nullable final DeviceDTO deviceDTO) {
|
||||
refreshRelayChannel(deviceDTO);
|
||||
refreshLine1Connect(deviceDTO);
|
||||
refreshLine2Connect(deviceDTO);
|
||||
refreshLine3Connect(deviceDTO);
|
||||
super.refreshDeviceState(deviceDTO);
|
||||
}
|
||||
}
|
@ -0,0 +1,411 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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 static org.openhab.binding.enphase.internal.EnphaseBindingConstants.CONFIG_HOSTNAME;
|
||||
import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.ENVOY_CHANNELGROUP_CONSUMPTION;
|
||||
import static org.openhab.binding.enphase.internal.EnphaseBindingConstants.ENVOY_WATTS_NOW;
|
||||
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 java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
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.core.cache.ExpiringCache;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.library.types.QuantityType;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
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.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* BridgeHandler for the Envoy gateway.
|
||||
*
|
||||
* @author Thomas Hentschel - Initial contribution
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EnvoyBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private enum FeatureStatus {
|
||||
UNKNOWN,
|
||||
SUPPORTED,
|
||||
UNSUPPORTED
|
||||
}
|
||||
|
||||
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 EnvoyConfiguration configuration = new EnvoyConfiguration();
|
||||
private @Nullable ScheduledFuture<?> updataDataFuture;
|
||||
private @Nullable ScheduledFuture<?> updateHostnameFuture;
|
||||
private @Nullable ExpiringCache<Map<String, @Nullable InverterDTO>> invertersCache;
|
||||
private @Nullable ExpiringCache<Map<String, @Nullable DeviceDTO>> devicesCache;
|
||||
private @Nullable EnvoyEnergyDTO productionDTO;
|
||||
private @Nullable EnvoyEnergyDTO consumptionDTO;
|
||||
private FeatureStatus consumptionSupported = FeatureStatus.UNKNOWN;
|
||||
private FeatureStatus jsonSupported = FeatureStatus.UNKNOWN;
|
||||
|
||||
public EnvoyBridgeHandler(final Bridge thing, final HttpClient httpClient,
|
||||
final EnvoyHostAddressCache envoyHostAddressCache) {
|
||||
super(thing);
|
||||
connector = new EnvoyConnector(httpClient);
|
||||
this.envoyHostnameCache = envoyHostAddressCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(final ChannelUID channelUID, final Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
refresh(channelUID);
|
||||
}
|
||||
}
|
||||
|
||||
private void refresh(final ChannelUID channelUID) {
|
||||
final EnvoyEnergyDTO data = ENVOY_CHANNELGROUP_CONSUMPTION.equals(channelUID.getGroupId()) ? consumptionDTO
|
||||
: productionDTO;
|
||||
|
||||
if (data == null) {
|
||||
updateState(channelUID, UnDefType.UNDEF);
|
||||
} else {
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
case ENVOY_WATT_HOURS_TODAY:
|
||||
updateState(channelUID, new QuantityType<>(data.wattHoursToday, Units.WATT_HOUR));
|
||||
break;
|
||||
case ENVOY_WATT_HOURS_SEVEN_DAYS:
|
||||
updateState(channelUID, new QuantityType<>(data.wattHoursSevenDays, Units.WATT_HOUR));
|
||||
break;
|
||||
case ENVOY_WATT_HOURS_LIFETIME:
|
||||
updateState(channelUID, new QuantityType<>(data.wattHoursLifetime, Units.WATT_HOUR));
|
||||
break;
|
||||
case ENVOY_WATTS_NOW:
|
||||
updateState(channelUID, new QuantityType<>(data.wattsNow, Units.WATT));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(EnphaseDevicesDiscoveryService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
configuration = getConfigAs(EnvoyConfiguration.class);
|
||||
if (!EnphaseBindingConstants.isValidSerial(configuration.serialNumber)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial number is not valid");
|
||||
return;
|
||||
}
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
connector.setConfiguration(configuration);
|
||||
consumptionSupported = FeatureStatus.UNKNOWN;
|
||||
jsonSupported = FeatureStatus.UNKNOWN;
|
||||
invertersCache = new ExpiringCache<>(Duration.of(configuration.refresh, ChronoUnit.MINUTES),
|
||||
this::refreshInverters);
|
||||
devicesCache = new ExpiringCache<>(Duration.of(configuration.refresh, ChronoUnit.MINUTES),
|
||||
this::refreshDevices);
|
||||
updataDataFuture = scheduler.scheduleWithFixedDelay(this::updateData, 0, configuration.refresh,
|
||||
TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called by the ExpiringCache when no inverter data is present to get the data from the Envoy gateway.
|
||||
* When there are connection problems it will start a scheduled job to try to reconnect to the
|
||||
*
|
||||
* @return the inverter data from the Envoy gateway or null if no data is available.
|
||||
*/
|
||||
private @Nullable Map<String, @Nullable InverterDTO> refreshInverters() {
|
||||
try {
|
||||
return connector.getInverters().stream()
|
||||
.collect(Collectors.toMap(InverterDTO::getSerialNumber, Function.identity()));
|
||||
} catch (final EnvoyNoHostnameException e) {
|
||||
// ignore hostname exception here. It's already handled by others.
|
||||
} catch (final EnvoyConnectionException e) {
|
||||
logger.trace("refreshInverters connection problem", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private @Nullable Map<String, @Nullable DeviceDTO> refreshDevices() {
|
||||
try {
|
||||
if (jsonSupported != FeatureStatus.UNSUPPORTED) {
|
||||
final Map<String, @Nullable DeviceDTO> devicesData = connector.getInventoryJson().stream()
|
||||
.flatMap(inv -> Stream.of(inv.devices).map(d -> {
|
||||
d.type = inv.type;
|
||||
return d;
|
||||
})).collect(Collectors.toMap(DeviceDTO::getSerialNumber, Function.identity()));
|
||||
|
||||
jsonSupported = FeatureStatus.SUPPORTED;
|
||||
return devicesData;
|
||||
}
|
||||
} catch (final EnvoyNoHostnameException e) {
|
||||
// ignore hostname exception here. It's already handled by others.
|
||||
} catch (final EnvoyConnectionException e) {
|
||||
if (jsonSupported == FeatureStatus.UNKNOWN) {
|
||||
logger.info(
|
||||
"This Ephase Envoy device ({}) doesn't seem to support json data. So not all channels are set.",
|
||||
getThing().getUID());
|
||||
jsonSupported = FeatureStatus.UNSUPPORTED;
|
||||
} else if (consumptionSupported == FeatureStatus.SUPPORTED) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data for the inverters. It get the data from cache or updates the cache if possible in case no data
|
||||
* is available.
|
||||
*
|
||||
* @param force force a cache refresh
|
||||
* @return data if present or null
|
||||
*/
|
||||
public @Nullable Map<String, @Nullable InverterDTO> getInvertersData(final boolean force) {
|
||||
final ExpiringCache<Map<String, @Nullable InverterDTO>> invertersCache = this.invertersCache;
|
||||
|
||||
if (invertersCache == null || !isOnline()) {
|
||||
return null;
|
||||
} else {
|
||||
if (force) {
|
||||
invertersCache.invalidateValue();
|
||||
}
|
||||
return invertersCache.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data for the devices. It get the data from cache or updates the cache if possible in case no data
|
||||
* is available.
|
||||
*
|
||||
* @param force force a cache refresh
|
||||
* @return data if present or null
|
||||
*/
|
||||
public @Nullable Map<String, @Nullable DeviceDTO> getDevices(final boolean force) {
|
||||
final ExpiringCache<Map<String, @Nullable DeviceDTO>> devicesCache = this.devicesCache;
|
||||
|
||||
if (devicesCache == null || !isOnline()) {
|
||||
return null;
|
||||
} else {
|
||||
if (force) {
|
||||
devicesCache.invalidateValue();
|
||||
}
|
||||
return devicesCache.getValue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called by the refresh thread.
|
||||
*/
|
||||
public synchronized void updateData() {
|
||||
try {
|
||||
updateInverters();
|
||||
updateEnvoy();
|
||||
updateDevices();
|
||||
} catch (final EnvoyNoHostnameException e) {
|
||||
scheduleHostnameUpdate(false);
|
||||
} catch (final EnvoyConnectionException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
scheduleHostnameUpdate(false);
|
||||
} catch (final RuntimeException e) {
|
||||
logger.debug("Unexpected error in Enphase {}: ", getThing().getUID(), e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateEnvoy() throws EnvoyNoHostnameException, EnvoyConnectionException {
|
||||
productionDTO = connector.getProduction();
|
||||
setConsumptionDTOData();
|
||||
getThing().getChannels().stream().map(Channel::getUID).filter(this::isLinked).forEach(this::refresh);
|
||||
if (isInitialized() && !isOnline()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve consumption data if supported, and keep track if this feature is supported by the device.
|
||||
*
|
||||
* @throws EnvoyConnectionException
|
||||
*/
|
||||
private void setConsumptionDTOData() throws EnvoyConnectionException {
|
||||
if (consumptionSupported != FeatureStatus.UNSUPPORTED && isOnline()) {
|
||||
try {
|
||||
consumptionDTO = connector.getConsumption();
|
||||
consumptionSupported = FeatureStatus.SUPPORTED;
|
||||
} catch (final EnvoyNoHostnameException e) {
|
||||
// ignore hostname exception here. It's already handled by others.
|
||||
} catch (final EnvoyConnectionException e) {
|
||||
if (consumptionSupported == FeatureStatus.UNKNOWN) {
|
||||
logger.info(
|
||||
"This Enphase Envoy device ({}) doesn't seem to support consumption data. So no consumption channels are set.",
|
||||
getThing().getUID());
|
||||
consumptionSupported = FeatureStatus.UNSUPPORTED;
|
||||
} else if (consumptionSupported == FeatureStatus.SUPPORTED) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates channels of the inverter things with inverter specific data.
|
||||
*/
|
||||
private void updateInverters() {
|
||||
final Map<String, @Nullable InverterDTO> inverters = getInvertersData(false);
|
||||
|
||||
if (inverters != null) {
|
||||
getThing().getThings().stream().map(Thing::getHandler).filter(h -> h instanceof EnphaseInverterHandler)
|
||||
.map(EnphaseInverterHandler.class::cast)
|
||||
.forEach(invHandler -> updateInverter(inverters, invHandler));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateInverter(final @Nullable Map<String, @Nullable InverterDTO> inverters,
|
||||
final EnphaseInverterHandler invHandler) {
|
||||
if (inverters == null) {
|
||||
return;
|
||||
}
|
||||
final InverterDTO inverterDTO = inverters.get(invHandler.getSerialNumber());
|
||||
|
||||
invHandler.refreshInverterChannels(inverterDTO);
|
||||
if (jsonSupported == FeatureStatus.UNSUPPORTED) {
|
||||
// if inventory json is supported device status is set in #updateDevices
|
||||
invHandler.refreshDeviceStatus(inverterDTO != null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates channels of the device things with device specific data.
|
||||
* This data is not available on all envoy devices.
|
||||
*/
|
||||
private void updateDevices() {
|
||||
final Map<String, @Nullable DeviceDTO> devices = getDevices(false);
|
||||
|
||||
getThing().getThings().stream().map(Thing::getHandler).filter(h -> h instanceof EnphaseDeviceHandler)
|
||||
.map(EnphaseDeviceHandler.class::cast).forEach(invHandler -> invHandler
|
||||
.refreshDeviceState(devices == null ? null : devices.get(invHandler.getSerialNumber())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a hostname update, but only schedules the task when not yet running or forced.
|
||||
* Force is used to reschedule the task and should only be used from within {@link #updateHostname()}.
|
||||
*
|
||||
* @param force if true will always schedule the task
|
||||
*/
|
||||
private synchronized void scheduleHostnameUpdate(final boolean force) {
|
||||
if (force || updateHostnameFuture == null) {
|
||||
logger.debug("Schedule hostname/ip address update for thing {} in {} seconds.", getThing().getUID(),
|
||||
RETRY_RECONNECT_SECONDS);
|
||||
updateHostnameFuture = scheduler.schedule(this::updateHostname, RETRY_RECONNECT_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerInitialized(final ThingHandler childHandler, final Thing childThing) {
|
||||
if (childHandler instanceof EnphaseInverterHandler) {
|
||||
updateInverter(getInvertersData(false), (EnphaseInverterHandler) childHandler);
|
||||
}
|
||||
if (childHandler instanceof EnphaseDeviceHandler) {
|
||||
final Map<String, @Nullable DeviceDTO> devices = getDevices(false);
|
||||
|
||||
if (devices != null) {
|
||||
((EnphaseDeviceHandler) childHandler)
|
||||
.refreshDeviceState(devices.get(((EnphaseDeviceHandler) childHandler).getSerialNumber()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a host name / ip address update.
|
||||
*/
|
||||
private void updateHostname() {
|
||||
final String lastKnownHostname = envoyHostnameCache.getLastKnownHostAddress(configuration.serialNumber);
|
||||
|
||||
if (lastKnownHostname.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"No ip address known of the envoy gateway. If this isn't updated in a few minutes check your connection.");
|
||||
scheduleHostnameUpdate(true);
|
||||
} else {
|
||||
final Configuration config = editConfiguration();
|
||||
|
||||
config.put(CONFIG_HOSTNAME, lastKnownHostname);
|
||||
logger.info("Enphase Envoy ({}) hostname/ip address set to {}", getThing().getUID(), lastKnownHostname);
|
||||
configuration.hostname = lastKnownHostname;
|
||||
connector.setConfiguration(configuration);
|
||||
updateConfiguration(config);
|
||||
updateData();
|
||||
// The task is done so the future can be released by setting it to null.
|
||||
updateHostnameFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
final ScheduledFuture<?> retryFuture = this.updateHostnameFuture;
|
||||
if (retryFuture != null) {
|
||||
retryFuture.cancel(true);
|
||||
}
|
||||
final ScheduledFuture<?> inverterFuture = this.updataDataFuture;
|
||||
|
||||
if (inverterFuture != null) {
|
||||
inverterFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns true if the bridge is online and not has an configuration pending.
|
||||
*/
|
||||
public boolean isOnline() {
|
||||
return getThing().getStatus() == ThingStatus.ONLINE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EnvoyBridgeHandler(" + thing.getUID() + ") Status: " + thing.getStatus();
|
||||
}
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2021 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.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Authentication;
|
||||
import org.eclipse.jetty.client.api.Authentication.Result;
|
||||
import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.util.DigestAuthentication;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
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.EnvoyNoHostnameException;
|
||||
import org.openhab.binding.enphase.internal.dto.EnvoyEnergyDTO;
|
||||
import org.openhab.binding.enphase.internal.dto.EnvoyErrorDTO;
|
||||
import org.openhab.binding.enphase.internal.dto.InventoryJsonDTO;
|
||||
import org.openhab.binding.enphase.internal.dto.InverterDTO;
|
||||
import org.openhab.binding.enphase.internal.dto.ProductionJsonDTO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* Methods to make API calls to the Envoy gateway.
|
||||
*
|
||||
* @author Hilbrand Bouwkamp - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class EnvoyConnector {
|
||||
|
||||
private static final String HTTP = "http://";
|
||||
private static final String PRODUCTION_JSON_URL = "/production.json";
|
||||
private static final String INVENTORY_JSON_URL = "/inventory.json";
|
||||
private static final String PRODUCTION_URL = "/api/v1/production";
|
||||
private static final String CONSUMPTION_URL = "/api/v1/consumption";
|
||||
private static final String INVERTERS_URL = PRODUCTION_URL + "/inverters";
|
||||
private static final long CONNECT_TIMEOUT_SECONDS = 5;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EnvoyConnector.class);
|
||||
private final Gson gson = new GsonBuilder().create();
|
||||
private final HttpClient httpClient;
|
||||
private String hostname = "";
|
||||
private @Nullable DigestAuthentication envoyAuthn;
|
||||
private @Nullable URI invertersURI;
|
||||
|
||||
public EnvoyConnector(final HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Envoy connection configuration.
|
||||
*
|
||||
* @param configuration the configuration to set
|
||||
*/
|
||||
public void setConfiguration(final EnvoyConfiguration configuration) {
|
||||
hostname = configuration.hostname;
|
||||
if (hostname.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
final String password = configuration.password.isEmpty()
|
||||
? EnphaseBindingConstants.defaultPassword(configuration.serialNumber)
|
||||
: configuration.password;
|
||||
final String username = configuration.username.isEmpty() ? EnvoyConfiguration.DEFAULT_USERNAME
|
||||
: configuration.username;
|
||||
final AuthenticationStore store = httpClient.getAuthenticationStore();
|
||||
|
||||
if (envoyAuthn != null) {
|
||||
store.removeAuthentication(envoyAuthn);
|
||||
}
|
||||
invertersURI = URI.create(HTTP + hostname + INVERTERS_URL);
|
||||
envoyAuthn = new DigestAuthentication(invertersURI, Authentication.ANY_REALM, username, password);
|
||||
store.addAuthentication(envoyAuthn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the production data from the Envoy gateway.
|
||||
*/
|
||||
public EnvoyEnergyDTO getProduction() throws EnvoyConnectionException, EnvoyNoHostnameException {
|
||||
return retrieveData(PRODUCTION_URL, this::jsonToEnvoyEnergyDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the consumption data from the Envoy gateway.
|
||||
*/
|
||||
public EnvoyEnergyDTO getConsumption() throws EnvoyConnectionException, EnvoyNoHostnameException {
|
||||
return retrieveData(CONSUMPTION_URL, this::jsonToEnvoyEnergyDTO);
|
||||
}
|
||||
|
||||
private @Nullable EnvoyEnergyDTO jsonToEnvoyEnergyDTO(final String json) {
|
||||
return gson.fromJson(json, EnvoyEnergyDTO.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the production/consumption data from the Envoy gateway.
|
||||
*/
|
||||
public ProductionJsonDTO getProductionJson() throws EnvoyConnectionException, EnvoyNoHostnameException {
|
||||
return retrieveData(PRODUCTION_JSON_URL, json -> gson.fromJson(json, ProductionJsonDTO.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the inventory data from the Envoy gateway.
|
||||
*/
|
||||
public List<InventoryJsonDTO> getInventoryJson() throws EnvoyConnectionException, EnvoyNoHostnameException {
|
||||
return retrieveData(INVENTORY_JSON_URL, this::jsonToEnvoyInventoryJson);
|
||||
}
|
||||
|
||||
private @Nullable List<InventoryJsonDTO> jsonToEnvoyInventoryJson(final String json) {
|
||||
final InventoryJsonDTO @Nullable [] list = gson.fromJson(json, InventoryJsonDTO[].class);
|
||||
|
||||
return list == null ? null : Arrays.asList(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the production data for the inverters.
|
||||
*/
|
||||
public List<InverterDTO> getInverters() throws EnvoyConnectionException, EnvoyNoHostnameException {
|
||||
synchronized (this) {
|
||||
final AuthenticationStore store = httpClient.getAuthenticationStore();
|
||||
final Result invertersResult = store.findAuthenticationResult(invertersURI);
|
||||
|
||||
if (invertersResult != null) {
|
||||
store.removeAuthenticationResult(invertersResult);
|
||||
}
|
||||
}
|
||||
return retrieveData(INVERTERS_URL, json -> Arrays.asList(gson.fromJson(json, InverterDTO[].class)));
|
||||
}
|
||||
|
||||
private synchronized <T> T retrieveData(final String urlPath, final Function<String, @Nullable T> jsonConverter)
|
||||
throws EnvoyConnectionException, EnvoyNoHostnameException {
|
||||
try {
|
||||
if (hostname.isEmpty()) {
|
||||
throw new EnvoyNoHostnameException("No host name/ip address known (yet)");
|
||||
}
|
||||
final URI uri = URI.create(HTTP + hostname + urlPath);
|
||||
logger.trace("Retrieving data from '{}'", uri);
|
||||
final Request request = httpClient.newRequest(uri).method(HttpMethod.GET).timeout(CONNECT_TIMEOUT_SECONDS,
|
||||
TimeUnit.SECONDS);
|
||||
final ContentResponse response = request.send();
|
||||
final String content = response.getContentAsString();
|
||||
|
||||
logger.trace("Envoy returned data for '{}' with status {}: {}", urlPath, response.getStatus(), content);
|
||||
try {
|
||||
if (response.getStatus() == HttpStatus.OK_200) {
|
||||
final T result = jsonConverter.apply(content);
|
||||
if (result == null) {
|
||||
throw new EnvoyConnectionException("No data received");
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
final @Nullable EnvoyErrorDTO error = gson.fromJson(content, EnvoyErrorDTO.class);
|
||||
|
||||
logger.debug("Envoy returned an error: {}", error);
|
||||
throw new EnvoyConnectionException(error == null ? response.getReason() : error.info);
|
||||
}
|
||||
} catch (final JsonSyntaxException e) {
|
||||
logger.debug("Error parsing json: {}", content, e);
|
||||
throw new EnvoyConnectionException("Error parsing data: ", e);
|
||||
}
|
||||
} catch (final InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new EnvoyConnectionException("Interrupted");
|
||||
} catch (final TimeoutException e) {
|
||||
logger.debug("TimeoutException: {}", e.getMessage());
|
||||
throw new EnvoyConnectionException("Connection timeout: ", e);
|
||||
} catch (final ExecutionException e) {
|
||||
logger.debug("ExecutionException: {}", e.getMessage(), e);
|
||||
throw new EnvoyConnectionException("Could not retrieve data: ", e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="enphase" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Enphase Envoy Binding</name>
|
||||
<description>This is the binding for Enphase Envoy solar panels.</description>
|
||||
|
||||
</binding:binding>
|
@ -0,0 +1,80 @@
|
||||
error.nodata=No Data
|
||||
envoy.global.ok=Normal
|
||||
|
||||
envoy.cond_flags.acb_ctrl.bmuhardwareerror=BMU Hardware Error
|
||||
envoy.cond_flags.acb_ctrl.bmuimageerror=BMU Image Error
|
||||
envoy.cond_flags.acb_ctrl.bmumaxcurrentwarning=BMU Max Current Warning
|
||||
envoy.cond_flags.acb_ctrl.bmusenseerror=BMU Sense Error
|
||||
|
||||
envoy.cond_flags.acb_ctrl.cellmaxtemperror=Cell Max Temperature Error
|
||||
envoy.cond_flags.acb_ctrl.cellmaxtempwarning=Cell Max Temperature Warning
|
||||
envoy.cond_flags.acb_ctrl.cellmaxvoltageerror=Cell Max Voltage Error
|
||||
envoy.cond_flags.acb_ctrl.cellmaxvoltagewarning=Cell Max Voltage Warning
|
||||
envoy.cond_flags.acb_ctrl.cellmintemperror=Cell Min Temperature Error
|
||||
envoy.cond_flags.acb_ctrl.cellmintempwarning=Cell Min Temperature Warning
|
||||
envoy.cond_flags.acb_ctrl.cellminvoltageerror=Cell Min Voltage Error
|
||||
envoy.cond_flags.acb_ctrl.cellminvoltagewarning=Cell Min Voltage Warning
|
||||
envoy.cond_flags.acb_ctrl.cibcanerror=CIB CAN Error
|
||||
envoy.cond_flags.acb_ctrl.cibimageerror=CIB Image Error
|
||||
envoy.cond_flags.acb_ctrl.cibspierror=CIB SPI Error"
|
||||
envoy.cond_flags.obs_strs.discovering=Discovering
|
||||
envoy.cond_flags.obs_strs.failure=Failure to report
|
||||
envoy.cond_flags.obs_strs.flasherror=Flash Error
|
||||
envoy.cond_flags.obs_strs.notmonitored=Not Monitored
|
||||
envoy.cond_flags.obs_strs.ok=Normal
|
||||
envoy.cond_flags.obs_strs.plmerror=PLM Error
|
||||
envoy.cond_flags.obs_strs.secmodeenterfailure=Secure mode enter failure
|
||||
envoy.cond_flags.obs_strs.secmodeexitfailure=Secure mode exit failure
|
||||
envoy.cond_flags.obs_strs.sleeping=Sleeping"
|
||||
|
||||
envoy.cond_flags.pcu_chan.acMonitorError=AC Monitor Error
|
||||
envoy.cond_flags.pcu_chan.acfrequencyhigh=AC Frequency High
|
||||
envoy.cond_flags.pcu_chan.acfrequencylow=AC Frequency Low
|
||||
envoy.cond_flags.pcu_chan.acfrequencyoor=AC Frequency Out Of Range
|
||||
envoy.cond_flags.pcu_chan.acvoltage_avg_hi=AC Voltage Average High
|
||||
envoy.cond_flags.pcu_chan.acvoltagehigh=AC Voltage High
|
||||
envoy.cond_flags.pcu_chan.acvoltagelow=AC Voltage Low
|
||||
envoy.cond_flags.pcu_chan.acvoltageoor=AC Voltage Out Of Range
|
||||
envoy.cond_flags.pcu_chan.acvoltageoosp1=AC Voltage Out Of Range - Phase 1
|
||||
envoy.cond_flags.pcu_chan.acvoltageoosp2=AC Voltage Out Of Range - Phase 2
|
||||
envoy.cond_flags.pcu_chan.acvoltageoosp3=AC Voltage Out Of Range - Phase 3
|
||||
envoy.cond_flags.pcu_chan.agfpowerlimiting=AGF Power Limiting
|
||||
envoy.cond_flags.pcu_chan.dcresistancelow=DC Resistance Low
|
||||
envoy.cond_flags.pcu_chan.dcresistancelowpoweroff=DC Resistance Low - Power Off
|
||||
envoy.cond_flags.pcu_chan.dcvoltagetoohigh=DC Voltage Too High
|
||||
envoy.cond_flags.pcu_chan.dcvoltagetoolow=DC Voltage Too Low
|
||||
envoy.cond_flags.pcu_chan.dfdt=AC Frequency Changing too Fast
|
||||
envoy.cond_flags.pcu_chan.gfitripped=GFI Tripped
|
||||
envoy.cond_flags.pcu_chan.gridgone=Grid Gone
|
||||
envoy.cond_flags.pcu_chan.gridinstability=Grid Instability
|
||||
envoy.cond_flags.pcu_chan.gridoffsethi=Grid Offset Hi
|
||||
envoy.cond_flags.pcu_chan.gridoffsetlow=Grid Offset Low
|
||||
envoy.cond_flags.pcu_chan.hardwareError=Hardware Error
|
||||
envoy.cond_flags.pcu_chan.hardwareWarning=Hardware Warning
|
||||
envoy.cond_flags.pcu_chan.highskiprate=High Skip Rate
|
||||
envoy.cond_flags.pcu_chan.invalidinterval=Invalid Interval
|
||||
envoy.cond_flags.pcu_chan.pwrgenoffbycmd=Power generation off by command
|
||||
envoy.cond_flags.pcu_chan.skippedcycles=Skipped Cycles
|
||||
envoy.cond_flags.pcu_chan.vreferror=Voltage Ref Error"
|
||||
|
||||
envoy.cond_flags.pcu_ctrl.alertactive=Alert Active
|
||||
envoy.cond_flags.pcu_ctrl.altpwrgenmode=Alternate Power Generation Mode
|
||||
envoy.cond_flags.pcu_ctrl.altvfsettings=Alternate Voltage and Frequency Settings
|
||||
envoy.cond_flags.pcu_ctrl.badflashimage=Bad Flash Image
|
||||
envoy.cond_flags.pcu_ctrl.bricked=No Grid Profile
|
||||
envoy.cond_flags.pcu_ctrl.commandedreset=Commanded Reset
|
||||
envoy.cond_flags.pcu_ctrl.criticaltemperature=Critical Temperature
|
||||
envoy.cond_flags.pcu_ctrl.dc-pwr-low=DC Power Too Low
|
||||
envoy.cond_flags.pcu_ctrl.iuplinkproblem=IUP Link Problem
|
||||
envoy.cond_flags.pcu_ctrl.manutestmode=In Manu Test Mode
|
||||
envoy.cond_flags.pcu_ctrl.nsync=Grid Perturbation Unsynchronized
|
||||
envoy.cond_flags.pcu_ctrl.overtemperature=Over Temperature
|
||||
envoy.cond_flags.pcu_ctrl.poweronreset=Power On Reset
|
||||
envoy.cond_flags.pcu_ctrl.pwrgenoffbycmd=Power generation off by command
|
||||
envoy.cond_flags.pcu_ctrl.runningonac=Running on AC
|
||||
envoy.cond_flags.pcu_ctrl.tpmtest=Transient Grid Profile
|
||||
envoy.cond_flags.pcu_ctrl.unexpectedreset=Unexpected Reset
|
||||
envoy.cond_flags.pcu_ctrl.watchdogreset=Watchdog Reset
|
||||
|
||||
envoy.cond_flags.rgm_chan.check_meter=Meter Error
|
||||
envoy.cond_flags.rgm_chan.power_quality=Poor Power Quality
|
@ -0,0 +1,228 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="enphase"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
|
||||
<bridge-type id="envoy">
|
||||
<label>Envoy</label>
|
||||
<description>Envoy gateway</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="production" typeId="envoy-data">
|
||||
<label>Production</label>
|
||||
<description>Production data from the solar panels</description>
|
||||
</channel-group>
|
||||
<channel-group id="consumption" typeId="envoy-data">
|
||||
<label>Consumption</label>
|
||||
<description>Consumption data from the solar panels</description>
|
||||
</channel-group>
|
||||
</channel-groups>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="serialNumber" type="text" pattern="[0-9]{12}" required="true">
|
||||
<label>Serial Number</label>
|
||||
<description>The serial number of the Envoy gateway which can be found on the gateway</description>
|
||||
</parameter>
|
||||
<parameter name="hostname" type="text">
|
||||
<label>Host Name / IP Address</label>
|
||||
<description>The host name/ip address of the Envoy gateway. Leave empty to auto detect</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="username" type="text">
|
||||
<label>User Name</label>
|
||||
<description>The user name to the Envoy gateway. Leave empty when using the default user name</description>
|
||||
<default>envoy</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="password" type="text">
|
||||
<context>password</context>
|
||||
<label>Password</label>
|
||||
<description>The password to the Envoy gateway. Leave empty when using the default password</description>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="refresh" type="integer" unit="min">
|
||||
<label>Refresh Time</label>
|
||||
<description>Period between updates. The default is 5 minutes, the refresh frequency of the Envoy itself</description>
|
||||
<default>5</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
|
||||
<thing-type id="inverter">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="envoy"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Inverter</label>
|
||||
<description>Inverter</description>
|
||||
|
||||
<channels>
|
||||
<channel id="lastReportWatts" typeId="last-report-watts"/>
|
||||
<channel id="maxReportWatts" typeId="max-report-watts"/>
|
||||
<channel id="lastReportDate" typeId="last-report-date"/>
|
||||
<channel id="status" typeId="status"/>
|
||||
<channel id="producing" typeId="producing"/>
|
||||
<channel id="communicating" typeId="communicating"/>
|
||||
<channel id="provisioned" typeId="provisioned"/>
|
||||
<channel id="operating" typeId="operating"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="partNumber"/>
|
||||
</properties>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="serialNumber" type="text" pattern="[0-9]{12}" required="true">
|
||||
<label>Serial Number</label>
|
||||
<description>The serial number of the inverter</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="relay">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="envoy"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Relay Controller</label>
|
||||
<description>Network system relay controller</description>
|
||||
|
||||
<channels>
|
||||
<channel id="relay" typeId="relay"/>
|
||||
<channel id="line1Connected" typeId="line-connected">
|
||||
<label>Line 1 Connection Status</label>
|
||||
</channel>
|
||||
<channel id="line2Connected" typeId="line-connected">
|
||||
<label>Line 2 Connection Status</label>
|
||||
</channel>
|
||||
<channel id="line3Connected" typeId="line-connected">
|
||||
<label>Line 3 Connection Status</label>
|
||||
</channel>
|
||||
<channel id="status" typeId="status"/>
|
||||
<channel id="producing" typeId="producing"/>
|
||||
<channel id="communicating" typeId="communicating"/>
|
||||
<channel id="provisioned" typeId="provisioned"/>
|
||||
<channel id="operating" typeId="operating"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="partNumber"/>
|
||||
</properties>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="serialNumber" type="text" pattern="[0-9]{12}" required="true">
|
||||
<label>Serial Number</label>
|
||||
<description>The serial number of the inverter</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<!-- Envoy gateway channels -->
|
||||
<channel-group-type id="envoy-data">
|
||||
<label>Envoy Data</label>
|
||||
<channels>
|
||||
<channel id="wattHoursToday" typeId="watt-hours-today"/>
|
||||
<channel id="wattHoursSevenDays" typeId="watt-hours-seven-days"/>
|
||||
<channel id="wattHoursLifetime" typeId="watt-hours-lifetime"/>
|
||||
<channel id="wattsNow" typeId="watts-now"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-type id="watt-hours-today">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Produced Today</label>
|
||||
<description>Watt hours produced today</description>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="watt-hours-seven-days">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Produced 7 Days</label>
|
||||
<description>Watt hours produced the last 7 days</description>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="watt-hours-lifetime">
|
||||
<item-type>Number:Energy</item-type>
|
||||
<label>Produced Lifetime</label>
|
||||
<description>Watt hours produced over the lifetime</description>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="watts-now">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Latest Power</label>
|
||||
<description>Latest watts produced</description>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<!-- Inverter channels -->
|
||||
<channel-type id="last-report-watts">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Last Report</label>
|
||||
<description>Last reported power delivery</description>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="max-report-watts">
|
||||
<item-type>Number:Power</item-type>
|
||||
<label>Max Report</label>
|
||||
<description>Maximum reported power</description>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="last-report-date">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Report Date</label>
|
||||
<description>Date of last reported power delivery</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<!-- Relay channels -->
|
||||
<channel-type id="relay">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Relay Status</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="line-connected">
|
||||
<item-type>Contact</item-type>
|
||||
<label>Line Connection Status</label>
|
||||
<description>When closed power line is connected</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<!-- Generic device channels -->
|
||||
<channel-type id="status">
|
||||
<item-type>String</item-type>
|
||||
<label>Status</label>
|
||||
<description>The status of the Enphase device</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="producing" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Producing</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="communicating" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Communicating</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="provisioned" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Provisioned</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="operating" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Operating</label>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -100,6 +100,7 @@
|
||||
<module>org.openhab.binding.energenie</module>
|
||||
<module>org.openhab.binding.enigma2</module>
|
||||
<module>org.openhab.binding.enocean</module>
|
||||
<module>org.openhab.binding.enphase</module>
|
||||
<module>org.openhab.binding.enturno</module>
|
||||
<module>org.openhab.binding.epsonprojector</module>
|
||||
<module>org.openhab.binding.etherrain</module>
|
||||
|
Loading…
Reference in New Issue
Block a user