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

[pixometer] Pixometer Binding initial contribution #4162

Merged
merged 18 commits into from
Nov 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
/bundles/org.openhab.binding.pentair/ @jsjames
/bundles/org.openhab.binding.phc/ @gnlpfjh
/bundles/org.openhab.binding.pioneeravr/ @Stratehm
/bundles/org.openhab.binding.pixometer/ @Confectrician
/bundles/org.openhab.binding.pjlinkdevice/ @nils
/bundles/org.openhab.binding.plclogo/ @falkena
/bundles/org.openhab.binding.plugwise/ @wborn
Expand Down
5 changes: 5 additions & 0 deletions bom/openhab-addons/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,11 @@
<artifactId>org.openhab.binding.pioneeravr</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.pixometer</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.pjlinkdevice</artifactId>
Expand Down
32 changes: 32 additions & 0 deletions bundles/org.openhab.binding.pixometer/.classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<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.pixometer/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.pixometer</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.pixometer/NOTICE
Original file line number Diff line number Diff line change
@@ -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/openhab2-addons
65 changes: 65 additions & 0 deletions bundles/org.openhab.binding.pixometer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Pixometer Binding

This binding connects to the pixometer API, which can manage your meter readings through a native smartphone app.

## Supported Things

This binding supports the following thing types according to the capabilities of pixometer:

| Name | Type | Description |
|-------------|--------|-----------------------------------------------------------------------------|
| Account | Bridge | Representation of a pixometer account, which connects to the pixometer API. |
| Energymeter | Thing | Provides access to the readings of configured energy meters. |
| Gasmeter | Thing | Provides access to the readings of configured gas meters. |
| Watermeter | Thing | Provides access to the readings of configured water meters. |

Confectrician marked this conversation as resolved.
Show resolved Hide resolved
The different meter types are pretty similar in basic, but are implemented in parallel to provide Units of Measurement support.

## Thing Configuration

### Account (bridge)

| Parameter | Description | Required | Default Value | Comment |
|--------------|--------------------------------------------------------------------|----------|------------------|---------------------------------------------------------------|
| user | | Yes | - | |
| password | | Yes | - | |
| refresh | Sets the refresh time. Minimum is 60 Minutes. | Yes | 240 | |

### Meter Things

| Parameter | Description | Required |
|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|
| resource_id | The ID which represents the current meter. You can find it in the pixometer browser app while editing a specific meter. It should look like this: "https://pixometer.io/portal/#/meters/XXXXX/edit" | Yes |

## Channels

All meter things have the following channels:

| Channel ID | Channel Description | Supported item type | Advanced |
|--------------------|--------------------------------------------------------|---------------------|----------|
| last_reading_value | The last value that has been read for this meter. | Number | false |
| last_reading_date | The time at which the last reading value was recorded. | DateTime | false |
| last_refresh_date | The last time that the current thing has been updated. | DateTime | false |

## Full Example

pixometer.things:

```java
Bridge pixometer:account:AccountName "MyAccountName" [ user="xxxxxxxx@xxxx.xx", password="xxxxxxxxxxxx", refresh= 60 ] {
Thing energymeter MeterName1 "MyMeterName1" [ resource_id = "xxxxxxxx" ]
Thing gasmeter MeterName2 "MyMeterName2" [ resource_id = "xxxxxxxx" ]
Thing watermeter MeterName3 "MyMeterName3" [ resource_id = "xxxxxxxx" ]
}
```

pixometer.items:

```java
Number:Volume Meter_Gas_ReadingValue "[.3%f %unit%]" [] {channel="pixometer:gasmeter:accountname:metername1:last_reading_value"}
DateTime Meter_Gas_LastReadingDate "[%1$td.%1$tm.%1$tY %1$tH:%1$tM]" [] {channel="pixometer:gasmeter:accountname:metername1:last_reading_date"}
Number:Energy Meter_Electricity_ReadingValue "[.3%f %unit%]" [] {channel="pixometer:energymeter:accountname:metername2:last_reading_value"}
DateTime Meter_Electricity_LastReadingDate "[%1$td.%1$tm.%1$tY %1$tH:%1$tM]" [] {channel="pixometer:energymeter:accountname:metername2:last_reading_date"}
Number:Volume Meter_Water_ReadingValue "[.3%f %unit%]" [] {channel="pixometer:watermeter:accountname:metername3:last_reading_value"}
DateTime Meter_Water_LastReadingDate "[%1$td.%1$tm.%1$tY %1$tH:%1$tM]" [] {channel="pixometer:watermeter:accountname:metername3:last_reading_date"}
```
16 changes: 16 additions & 0 deletions bundles/org.openhab.binding.pixometer/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?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/maven-v4_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.0-SNAPSHOT</version>
</parent>

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

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

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.pixometer-${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/${project.version}/xml/features</repository>

<feature name="openhab-binding-pixometer" description="pixometer Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.pixometer/${project.version}</bundle>
</feature>
</features>
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/**
* Copyright (c) 2010-2019 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.pixometer.handler;

import static org.openhab.binding.pixometer.internal.PixometerBindingConstants.*;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.smarthome.core.thing.Bridge;
import org.eclipse.smarthome.core.thing.ChannelUID;
import org.eclipse.smarthome.core.thing.ThingStatus;
import org.eclipse.smarthome.core.thing.ThingStatusDetail;
import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.io.net.http.HttpUtil;
import org.openhab.binding.pixometer.internal.config.PixometerAccountConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
* The {@link AccountHandler} is responsible for handling the api connection and authorization (including token
* refresh)
*
* @author Jerome Luckenbach - Initial contribution
*/
@NonNullByDefault
public class AccountHandler extends BaseBridgeHandler {

private final Logger logger = LoggerFactory.getLogger(this.getClass());
private static final int TOKEN_MIN_DIFF_MS = (int) TimeUnit.DAYS.toMillis(2);
private final JsonParser jsonParser = new JsonParser();

private @NonNullByDefault({}) String authToken;
private int refreshInterval;
private long tokenExpiryDate;

public AccountHandler(Bridge bridge) {
super(bridge);
}

@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// Nothing to handle here currently
}

@Override
public void initialize() {
logger.debug("Initialize Pixometer Accountservice");

PixometerAccountConfiguration config = getConfigAs(PixometerAccountConfiguration.class);
setRefreshInterval(config.refresh);
String user = config.user;
String password = config.password;
String scope = "read"; // Prepared for config value

// Check expiry date every Day and obtain new access token if difference is less then or equal to 2 days
scheduler.scheduleWithFixedDelay(() -> {
logger.debug("Checking if new access token is needed...");
try {
long difference = getTokenExpiryDate() - System.nanoTime();
if (difference <= TOKEN_MIN_DIFF_MS) {
obtainAuthTokenAndExpiryDate(user, password, scope);
}
} catch (RuntimeException r) {
logger.debug("Could not check token expiry date for Thing {}: {}", getThing().getUID(), r.getMessage(),
r);
}
}, 1, TimeUnit.DAYS.toMinutes(1), TimeUnit.MINUTES);

logger.debug("Refresh job scheduled to run every days. for '{}'", getThing().getUID());
}

@Override
public void updateStatus(ThingStatus status) {
super.updateStatus(status);
}

/**
* Request auth token with read or write access.
* (Write access is prepared for a possible later usage for updating meters.)
*
* @param user The username to use
* @param password The corresponding password
* @param scope The granted scope on the api for the binding
*/
private void obtainAuthTokenAndExpiryDate(String user, String password, String scope) {
try {
String url = API_BASE_URL + "v1/access-token/";
Properties urlHeader = (Properties) new Properties().put("CONTENT-TYPE", "application/json");

JsonObject httpBody = new JsonObject();
httpBody.addProperty("username", user);
httpBody.addProperty("password", password);
httpBody.addProperty("scope", scope);

InputStream content = new ByteArrayInputStream(httpBody.toString().getBytes(StandardCharsets.UTF_8));
String urlResponse = HttpUtil.executeUrl("POST", url, urlHeader, content, "application/json", 2000);
JsonObject responseJson = (JsonObject) jsonParser.parse(urlResponse);

if (responseJson.has(AUTH_TOKEN)) {
// Store the expire date for automatic token refresh
int expiresIn = Integer.parseInt(responseJson.get("expires_in").toString());
setTokenExpiryDate(TimeUnit.SECONDS.toNanos(expiresIn));

setAuthToken(responseJson.get(AUTH_TOKEN).toString().replaceAll("\"", ""));

updateStatus(ThingStatus.ONLINE);
return;
}

String errorMsg = String.format("Invalid Api Response ( %s )", responseJson);

throw new IOException(errorMsg);
} catch (IOException e) {
String errorMsg = String.format(
"Could not obtain auth token. Please check your configured account credentials. %s %s",
this.getThing().getUID(), e.getMessage());

logger.debug(errorMsg, e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMsg);
}
}

/**
* Getters and Setters
*/

public String getAuthToken() {
return authToken;
}

private void setAuthToken(String authToken) {
this.authToken = authToken;
}

public int getRefreshInterval() {
return refreshInterval;
}

private void setRefreshInterval(int refreshInterval) {
this.refreshInterval = refreshInterval;
}

public long getTokenExpiryDate() {
return tokenExpiryDate;
}

private void setTokenExpiryDate(long expiresIn) {
this.tokenExpiryDate = System.nanoTime() + expiresIn;
}

}
Loading