Skip to content

Commit

Permalink
[pixometer] Pixometer Binding initial contribution (openhab#4162)
Browse files Browse the repository at this point in the history
Signed-off-by: Jerome Luckenbach <github@luckenba.ch>
  • Loading branch information
Confectrician authored and hww3 committed Jan 25, 2020
1 parent 1ea61e1 commit de4c928
Show file tree
Hide file tree
Showing 29 changed files with 1,783 additions and 0 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,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 @@ -655,6 +655,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. |

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

0 comments on commit de4c928

Please sign in to comment.