Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions bundles/org.openhab.binding.homewizard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,6 @@ There are two important points of attention:
1. For API v1, the local API of each device must be enabled and a fixed address must be configured for the devices.
1. For API v2, a bearer token needs to be obtained from the device. See <https://api-documentation.homewizard.com/> for instructions how to obtain a token.

### Local API v1

The local API of a device can be enabled from the HomeWizard app.
Go to Settings in the app, then Meters and select the device you want to enable.
On this page enable the local API.

### Local API v2

This version is still in beta. Currently hwe-p1 and hwe-bat are supported.

### Fixed Address

Expand Down Expand Up @@ -52,7 +43,7 @@ All devices can be configured through the web interface.
|--------------|----------|---------|---------------------------------------------------------------------------------------------------|
| ipAddress | * | | This specifies the IP address (or host name) where the meter can be found. |
| refreshDelay | | 5 | This specifies the interval in seconds used by the binding to read updated values from the meter. |
| apiVersion | * | v1 | The API version to be used. v2 is still in beta but is already supported in this binding. |
| apiVersion | * | v1 | The API version to be used. |
| bearerToken | | | The bearer token to be used when using API v2. |

Note that update rate of the P1 Meter itself depends on the frequency of the telegrams it receives from the Smart Meter.
Expand Down Expand Up @@ -95,6 +86,11 @@ For DSMR5 meters this is generally once per second, for older versions the frequ
| active_liter | Number:VolumetricFlowRate | This channel provides the active water usage in liters per minute. | hwe-wtr |
| state_of_charge | Number:Dimensionless | This channel provides access to the current state of charge in percent. | hwe-bat |
| cycles | Number:Dimensionless | This channel provides access to the number of battery cycles. | hwe-bat |
| batteries_mode | String | This channel provides the control mode of the Plug-In Battery. | hwe-p1 |
| batteries_power | Number:Power | This channel provides the current combined power consumption/production of the ontrolled Plug-In Batteries. | hwe-p1 |
| batteries_target_power | Number:Power | This channel provides the target power consumption/production of the controlled Plug-In Batteries. | hwe-p1 |
| batteries_max_consumption | Number:Power | This channel provides the maximum allowed consumption power of the controlled Plug-In Batteries. | hwe-p1 |
| batteries_max_production | Number:Power | This channel provides the maximum allowed production power of the controlled Plug-In Batteries. | hwe-p1 |

## Full Example

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public class HomeWizardBindingConstants {

// Channel Groups
public static final String CHANNEL_GROUP_ENERGY = "energy";
public static final String CHANNEL_GROUP_P1_BATTERIES = "batteries";
public static final String CHANNEL_GROUP_WATER = "water";
public static final String CHANNEL_GROUP_SKT_CONTROL = "control";

Expand Down Expand Up @@ -104,6 +105,12 @@ public class HomeWizardBindingConstants {
public static final String CHANNEL_GAS_TIMESTAMP = "gas_timestamp";
public static final String CHANNEL_GAS_TOTAL = "total_gas";

public static final String CHANNEL_BATTERIES_MODE = "batteries_mode";
public static final String CHANNEL_BATTERIES_POWER = "batteries_power";
public static final String CHANNEL_BATTERIES_TARGET_POWER = "batteries_target_power";
public static final String CHANNEL_BATTERIES_MAX_CONSUMPTION = "batteries_max_consumption";
public static final String CHANNEL_BATTERIES_MAX_PRODUCTION = "batteries_max_production";

// Energy Socket And kWh Meter Channels
public static final String CHANNEL_REACTIVE_POWER = "reactive_power";
public static final String CHANNEL_APPARENT_POWER = "apparent_power";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,19 @@
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.openhab.binding.homewizard.internal.HomeWizardConfiguration;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -116,7 +122,7 @@ public void initialize() {

if (configure() && processDeviceInformation()) {
updateStatus(ThingStatus.UNKNOWN);
pollingJob = executorService.scheduleWithFixedDelay(this::pollingCode, 0, config.refreshDelay,
pollingJob = executorService.scheduleWithFixedDelay(this::retrieveData, 0, config.refreshDelay,
TimeUnit.SECONDS);
}
}
Expand Down Expand Up @@ -155,6 +161,21 @@ private boolean configure() {
return true;
}

/**
* Not listening to any commands.
*/
// @Override
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}

/**
* The actual polling loop
*/
protected void retrieveData() {
retrieveMeasurementData();
}

private boolean processDeviceInformation() {
String deviceInformation = "";

Expand Down Expand Up @@ -226,19 +247,29 @@ protected void updateState(String groupID, String channelID, State state) {
}

/**
* Device specific handling of the returned data.
* Device specific handling of the returned measurement data.
*
* @param payload The data obtained form the API call
*/
protected abstract void handleDataPayload(String data);
protected abstract void handleMeasurementData(String data);

protected ContentResponse putDataTo(String url, String data)
throws InterruptedException, TimeoutException, ExecutionException {
var request = httpClient.newRequest(url).method(HttpMethod.PUT).content(new StringContentProvider(data));

return sendRequest(request);
}

protected ContentResponse getResponseFrom(String url)
throws InterruptedException, TimeoutException, ExecutionException {
var request = httpClient.newRequest(url);
return sendRequest(httpClient.newRequest(url));
}

private ContentResponse sendRequest(Request request)
throws InterruptedException, TimeoutException, ExecutionException {
if (config.apiVersion > 1) {
request = request.header(HttpHeader.AUTHORIZATION, BEARER + " " + config.bearerToken);
request = request.header(API_VERSION_HEADER, "" + config.apiVersion);
request.header(HttpHeader.AUTHORIZATION, BEARER + " " + config.bearerToken);
request.header(API_VERSION_HEADER, "" + config.apiVersion);
}
return request.timeout(20, TimeUnit.SECONDS).send();
}
Expand All @@ -250,7 +281,7 @@ protected ContentResponse getResponseFrom(String url)
public String getDeviceInformationData()
throws InterruptedException, TimeoutException, ExecutionException, SecurityException {
var response = getResponseFrom(apiURL);
if (response.getStatus() == 401) {
if (response.getStatus() == HttpStatus.UNAUTHORIZED_401) {
throw new SecurityException("Bearer token is invalid.");
}
return response.getContentAsString();
Expand All @@ -267,29 +298,19 @@ public String getMeasurementData() throws InterruptedException, TimeoutException
} else {
url += "measurement";
}

return getResponseFrom(url).getContentAsString();
}

protected void pollData() {
protected void retrieveMeasurementData() {
final String measurementData;

try {
measurementData = getMeasurementData();
} catch (Exception e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
String.format("Device is offline or doesn't support the API version"));
return;
}

updateStatus(ThingStatus.ONLINE);
handleDataPayload(measurementData);
}

/**
* The actual polling loop
*/
protected void pollingCode() {
pollData();
handleMeasurementData(measurementData);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
import org.openhab.binding.homewizard.internal.HomeWizardBindingConstants;
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;

/**
* The {@link HomeWizardEnergyMeterHandler} implements functionality generic to several energy meters.
Expand All @@ -38,20 +36,13 @@ public HomeWizardEnergyMeterHandler(Thing thing) {
super(thing);
}

/**
* Not listening to any commands.
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}

/**
* Device specific handling of the returned data.
*
* @param data The data obtained form the API call
*/
@Override
protected void handleDataPayload(String data) {
protected void handleMeasurementData(String data) {
var payload = gson.fromJson(data, HomeWizardEnergyMeterMeasurementPayload.class);
if (payload != null) {
if (!thing.getThingTypeUID().equals(HomeWizardBindingConstants.THING_TYPE_P1_METER)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ public void handleCommand(ChannelUID channelUID, Command command) {
* @param data The data obtained from the API call
*/
@Override
protected void handleDataPayload(String data) {
super.handleDataPayload(data);
protected void handleMeasurementData(String data) {
super.handleMeasurementData(data);

var payload = gson.fromJson(data, HomeWizardEnergySocketMeasurementPayload.class);
if (payload != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,13 @@
*/
package org.openhab.binding.homewizard.internal.devices.energy_socket;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.homewizard.internal.devices.HomeWizardEnergyMeterHandler;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;

import com.google.gson.JsonSyntaxException;

/**
* The {@link HomeWizardEnergySocketStateHandler} extends the base class
* to provide support for devices that also have a 'state' interface.
Expand All @@ -38,6 +31,8 @@
@NonNullByDefault
public abstract class HomeWizardEnergySocketStateHandler extends HomeWizardEnergyMeterHandler {

private final String STATE_URL = "v1/state";

/**
* Constructor
*
Expand All @@ -57,10 +52,10 @@ public HomeWizardEnergySocketStateHandler(Thing thing) {

/**
* @return json response from the state api
* @throws IOException
* @throws Exception
*/
public String getStateData() throws Exception {
return getResponseFrom(apiURL + "v1/state").getContentAsString();
return getResponseFrom(apiURL + STATE_URL).getContentAsString();
}

protected void pollState() {
Expand Down Expand Up @@ -96,21 +91,22 @@ protected void pollState() {
* @param command The command to send.
*/
protected @Nullable HomeWizardEnergySocketStatePayload sendStateCommand(String command) {
try (InputStream is = new ByteArrayInputStream(command.getBytes())) {
String updatedState = HttpUtil.executeUrl("PUT", apiURL + "v1/state", is, "application/json", 30000);
return gson.fromJson(updatedState, HomeWizardEnergySocketStatePayload.class);
} catch (IOException | JsonSyntaxException e) {
logger.warn("Failed to send command {} to {}", command, apiURL + "state");
return null;
String updatedState = "";
try {
updatedState = putDataTo(apiURL + STATE_URL, command).getContentAsString();
} catch (Exception ex) {
logger.warn("Failed to send command {} to {}", command, apiURL + STATE_URL);
}

return gson.fromJson(updatedState, HomeWizardEnergySocketStatePayload.class);
}

/*
* This overrides the original polling loop by including a request for the current state..
*/
@Override
protected void pollingCode() {
pollData();
protected void retrieveData() {
retrieveMeasurementData();
pollState();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ public HomeWizardKwhMeterHandler(Thing thing) {
* @param payload The data obtained form the API call
*/
@Override
protected void handleDataPayload(String data) {
super.handleDataPayload(data);
protected void handleMeasurementData(String data) {
super.handleMeasurementData(data);

var payload = gson.fromJson(data, HomeWizardEnergySocketMeasurementPayload.class);
if (payload != null) {
Expand Down
Loading