Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[iammeter] Iammeter Binding initial contribution #8252

Merged
merged 12 commits into from Sep 6, 2020
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 5 additions & 0 deletions bom/openhab-addons/pom.xml
Expand Up @@ -414,6 +414,11 @@
<artifactId>org.openhab.binding.hyperion</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.iammeter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.iaqualink</artifactId>
Expand Down
15 changes: 15 additions & 0 deletions bundles/org.openhab.binding.iammeter/.classpath
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/main/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
23 changes: 23 additions & 0 deletions bundles/org.openhab.binding.iammeter/.project
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.iammeter</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>
13 changes: 13 additions & 0 deletions bundles/org.openhab.binding.iammeter/NOTICE
@@ -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
57 changes: 57 additions & 0 deletions bundles/org.openhab.binding.iammeter/README.md
@@ -0,0 +1,57 @@
# Iammeter Binding

[Iammeter](https://www.iammeter.com) provides real-time readings of single-phase (WEM3080, WEM3162) and three-phase (WEM3080T) meters from IAMMETER over Wi-Fi.

## Use of the binding

The Iammeter is exposed as one thing with a number of channels that can be used to read the values for different aspects of your Iammeter devices.

## Setup of the binding

You can add the Iammeter device via the openHAB UI manually.


Hilbrand marked this conversation as resolved.
Show resolved Hide resolved
## Available channels

The following table is taken from the official manual and contains all available channels.

Single-phase energy meter (WEM3080/WEM3162)
| Name | Unit | Description | Type |
|----------------|------|------------------------------|--------------------------|
| voltage_a | V | Voltage | Number:ElectricPotential |
| current_a | A | Current | Number:ElectricCurrent |
| power_a | W | Active power | Number:Power |
| importenergy_a | kWh | Energy consumption from gird | Number:Energy |
| exportgrid_a | kWh | Energy export to grid | Number:Energy |


Three-phase energy meter (WEM3080T)
| Name | Unit | Description | Type |
|----------------|------|-----------------------|--------------------------|
| voltage_a | V | A phase voltage | Number:ElectricPotential |
| current_a | A | A phase current | Number:ElectricCurrent |
| power_a | W | A phase active power | Number:Power |
| importenergy_a | kWh | A phase import energy | Number:Energy |
| exportgrid_a | kWh | A phase export energy | Number:Energy |
| frequency_a | kWh | A phase frequency | Number:Frequency |
| pf_a | kWh | A phase power factor | Number |
| voltage_b | V | B phase voltage | Number:ElectricPotential |
| current_b | A | B phase current | Number:ElectricCurrent |
| power_b | W | B phase active power | Number:Power |
| importenergy_b | kWh | B phase import energy | Number:Energy |
| exportgrid_b | kWh | B phase export energy | Number:Energy |
| frequency_b | kWh | B phase frequency | Number:Frequency |
| pf_b | kWh | B phase power factor | Number |
| voltage_c | V | C phase voltage | Number:ElectricPotential |
| current_c | A | C phase current | Number:ElectricCurrent |
| power_c | W | C phase active power | Number:Power |
| importenergy_c | kWh | C phase import energy | Number:Energy |
| exportgrid_c | kWh | C phase export energy | Number:Energy |
| frequency_c | kWh | C phase frequency | Number:Frequency |
| pf_c | kWh | C phase power factor | Number |



Hilbrand marked this conversation as resolved.
Show resolved Hide resolved
## More information

More information about the Iammeter devices can be found in the [Iammeter website](https://www.iammeter.com).
17 changes: 17 additions & 0 deletions bundles/org.openhab.binding.iammeter/pom.xml
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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>2.5.9-SNAPSHOT</version>
</parent>

<artifactId>org.openhab.binding.iammeter</artifactId>

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

</project>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.iammeter-${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-iammeter" description="Iammeter Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.iammeter/${project.version}</bundle>
</feature>
</features>
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2010-2020 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.iammeter.internal;

import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.smarthome.core.thing.Channel;
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.types.State;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

/**
* The {@link IammeterHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Yang Bo - Initial contribution
*/

@NonNullByDefault
public class Iammeter3080THandler extends IammeterBaseHandler {

public Iammeter3080THandler(Thing thing) {
super(thing);
}

@SuppressWarnings("null")
@Override
protected void resolveData(String response) {
JsonElement iammeterDataElement = new JsonParser().parse(response);
JsonObject iammeterData = iammeterDataElement.getAsJsonObject();
String keyWord = "Datas";
if (iammeterData.has("Datas") && iammeterData.has("SN")) {
String groups[] = { "powerPhaseA", "powerPhaseB", "powerPhaseC" };
for (int row = 0; row < groups.length; row++) {
String gpName = groups[row];
List<Channel> chnList = getThing().getChannelsOfGroup(gpName);
for (IammeterWEM3080Channel channelConfig : IammeterWEM3080Channel.values()) {
Channel chnl = chnList.get(channelConfig.ordinal());
if (chnl != null) {
State state = getQuantityState(iammeterData.get(keyWord).getAsJsonArray().get(row)
.getAsJsonArray().get(channelConfig.ordinal()).toString(), channelConfig.getUnit());
updateState(chnl.getUID(), state);
}
}
updateStatus(ThingStatus.ONLINE);
}
}
}

}
@@ -0,0 +1,156 @@
/**
* Copyright (c) 2010-2020 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.iammeter.internal;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import javax.measure.Unit;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.core.cache.ExpiringCache;
import org.eclipse.smarthome.core.library.types.QuantityType;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.Thing;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
import org.eclipse.smarthome.core.thing.binding.BaseThingHandler;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.RefreshType;
import org.eclipse.smarthome.core.types.State;
import org.eclipse.smarthome.core.types.UnDefType;
import org.eclipse.smarthome.io.net.http.HttpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonSyntaxException;

/**
* The {@link IammeterHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Yang Bo - Initial contribution
*/

@NonNullByDefault
public class IammeterBaseHandler extends BaseThingHandler {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class IammeterBaseHandler extends BaseThingHandler {
public abstract class IammeterBaseHandler extends BaseThingHandler {

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

codes reformated using mvn spotless:apply.


private final Logger logger = LoggerFactory.getLogger(IammeterBaseHandler.class);
private @Nullable ScheduledFuture<?> refreshJob;
private IammeterConfiguration config;
private static final int TIMEOUT_MS = 5000;
private final ExpiringCache<Boolean> refreshCache = new ExpiringCache<>(Duration.ofSeconds(5), this::refresh);

public IammeterBaseHandler(Thing thing) {
super(thing);
config = getConfiguration();
}

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
refreshCache.getValue();
}
}

@Override
public void initialize() {
ScheduledFuture<?> refreshJob = this.refreshJob;
config = getConfiguration();
if (refreshJob == null) {
refreshJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, config.refreshInterval, TimeUnit.SECONDS);
this.refreshJob = refreshJob;
updateStatus(ThingStatus.UNKNOWN);
}
}

protected void resolveData(String response) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By maing this class abstract you can also make this method abstract:

Suggested change
protected void resolveData(String response) {
protected abstract void resolveData(String response);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed.


}

@SuppressWarnings("null")
private boolean refresh() {
refreshCache.invalidateValue();
IammeterConfiguration config = this.config;
try {
String httpMethod = "GET";
String url = "http://" + config.username + ":" + config.password + "@" + config.host + ":" + config.port
+ "/monitorjson";
String content = "";
InputStream stream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not used anymore and can be removed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.


String response = HttpUtil.executeUrl(httpMethod, url, TIMEOUT_MS);

resolveData(response);

// JsonElement iammeterDataElement = new JsonParser().parse(response);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove this commented out code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed.

// JsonObject iammeterData = iammeterDataElement.getAsJsonObject();
// String keyWord = "Datas";
// if (iammeterData.has("Datas") && iammeterData.has("SN")) {
// String groups[] = { "powerPhaseA", "powerPhaseB", "powerPhaseC" };
// for (int row = 0; row < groups.length; row++) {
// String gpName = groups[row];
// List<Channel> chnList = getThing().getChannelsOfGroup(gpName);
// for (IammeterWEM3080Channel channelConfig : IammeterWEM3080Channel.values()) {
// Channel chnl = chnList.get(channelConfig.ordinal());
// if (chnl != null) {
// State state = getQuantityState(iammeterData.get(keyWord).getAsJsonArray().get(row)
// .getAsJsonArray().get(channelConfig.ordinal()).toString(), channelConfig.getUnit());
// updateState(chnl.getUID(), state);
// }
// }
// updateStatus(ThingStatus.ONLINE);
// }
// }
stream.close();
updateStatus(ThingStatus.ONLINE);
return true;
// Very rudimentary Exception differentiation
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Communication error with the device: " + e.getMessage());
} catch (JsonSyntaxException je) {
logger.warn("Invalid JSON when refreshing source {}: {}", getThing().getUID(), je.getMessage());
updateStatus(ThingStatus.OFFLINE);
}
return false;
}

protected State getQuantityState(String value, Unit<?> unit) {
try {
return QuantityType.valueOf(Float.parseFloat(value), unit);
} catch (NumberFormatException e) {
return UnDefType.UNDEF;
}
}

@Override
public void dispose() {
ScheduledFuture<?> refreshJob = this.refreshJob;
if (refreshJob != null && !refreshJob.isCancelled()) {
refreshJob.cancel(true);
this.refreshJob = null;
}
super.dispose();
}

public IammeterConfiguration getConfiguration() {
return this.getConfigAs(IammeterConfiguration.class);
}
}