diff --git a/CODEOWNERS b/CODEOWNERS
index 8925e9d38db81..a99cd868a81cf 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -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
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 6979986fa1f36..b574a8563c063 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -655,6 +655,11 @@
org.openhab.binding.pioneeravr
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.pixometer
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.pjlinkdevice
diff --git a/bundles/org.openhab.binding.pixometer/.classpath b/bundles/org.openhab.binding.pixometer/.classpath
new file mode 100644
index 0000000000000..a5d95095ccaaf
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/.classpath
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.pixometer/.project b/bundles/org.openhab.binding.pixometer/.project
new file mode 100644
index 0000000000000..7c04a32837dd1
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/.project
@@ -0,0 +1,23 @@
+
+
+ org.openhab.binding.pixometer
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/bundles/org.openhab.binding.pixometer/NOTICE b/bundles/org.openhab.binding.pixometer/NOTICE
new file mode 100644
index 0000000000000..4c20ef446c1e4
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/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/openhab2-addons
diff --git a/bundles/org.openhab.binding.pixometer/README.md b/bundles/org.openhab.binding.pixometer/README.md
new file mode 100644
index 0000000000000..88d0815c89165
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/README.md
@@ -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"}
+```
diff --git a/bundles/org.openhab.binding.pixometer/pom.xml b/bundles/org.openhab.binding.pixometer/pom.xml
new file mode 100644
index 0000000000000..4e4792376d79a
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/pom.xml
@@ -0,0 +1,16 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 2.5.0-SNAPSHOT
+
+
+ org.openhab.binding.pixometer
+
+ openHAB Add-ons :: Bundles :: pixometer Binding
+
+
diff --git a/bundles/org.openhab.binding.pixometer/src/main/feature/feature.xml b/bundles/org.openhab.binding.pixometer/src/main/feature/feature.xml
new file mode 100644
index 0000000000000..eae87c3c4d304
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${project.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.pixometer/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/handler/AccountHandler.java b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/handler/AccountHandler.java
new file mode 100644
index 0000000000000..d524ba552479f
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/handler/AccountHandler.java
@@ -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;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/handler/MeterHandler.java b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/handler/MeterHandler.java
new file mode 100644
index 0000000000000..b9ead51d7be9f
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/handler/MeterHandler.java
@@ -0,0 +1,308 @@
+/**
+ * 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.IOException;
+import java.time.Duration;
+import java.util.Properties;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.measure.quantity.Energy;
+import javax.measure.quantity.Volume;
+
+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.library.unit.SIUnits;
+import org.eclipse.smarthome.core.library.unit.SmartHomeUnits;
+import org.eclipse.smarthome.core.thing.Bridge;
+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.ThingStatusInfo;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+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.io.net.http.HttpUtil;
+import org.openhab.binding.pixometer.internal.config.PixometerMeterConfiguration;
+import org.openhab.binding.pixometer.internal.config.ReadingInstance;
+import org.openhab.binding.pixometer.internal.data.MeterState;
+import org.openhab.binding.pixometer.internal.serializer.CustomReadingInstanceDeserializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+/**
+ * The {@link MeterHandler} is responsible for handling data and measurements of a meter thing
+ *
+ * @author Jerome Luckenbach - Initial contribution
+ */
+@NonNullByDefault
+public class MeterHandler extends BaseThingHandler {
+
+ private final Logger logger = LoggerFactory.getLogger(MeterHandler.class);
+
+ private static final String API_VERSION = "v1";
+ private static final String API_METER_ENDPOINT = "meters";
+ private static final String API_READINGS_ENDPOINT = "readings";
+
+ private final GsonBuilder gsonBuilder = new GsonBuilder().registerTypeAdapter(ReadingInstance.class,
+ new CustomReadingInstanceDeserializer());
+ private final Gson gson = gsonBuilder.create();
+ private final JsonParser jsonParser = new JsonParser();
+
+ private @NonNullByDefault({}) String resourceID;
+ private @NonNullByDefault({}) String meterID;
+ private @NonNullByDefault({}) ExpiringCache<@Nullable MeterState> cache;
+
+ private @Nullable ScheduledFuture> pollingJob;
+
+ public MeterHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ try {
+ if (command instanceof RefreshType) {
+ updateMeter(channelUID, cache.getValue());
+ } else {
+ logger.debug("The pixometer binding is read-only and can not handle command {}", command);
+ }
+ } catch (IOException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ }
+
+ @Override
+ public void initialize() {
+ logger.debug("Initializing Pixometer handler '{}'", getThing().getUID());
+ updateStatus(ThingStatus.UNKNOWN);
+
+ PixometerMeterConfiguration config = getConfigAs(PixometerMeterConfiguration.class);
+ setRessourceID(config.resourceId);
+
+ cache = new ExpiringCache<@Nullable MeterState>(Duration.ofMinutes(60), this::refreshCache);
+
+ Bridge b = this.getBridge();
+ if (b == null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "Could not find bridge (pixometer config). Did you choose one?");
+ return;
+ }
+
+ obtainMeterId();
+
+ // Start polling job with the interval, that has been set up in the bridge
+ int pollingPeriod = Integer.parseInt(b.getConfiguration().get(CONFIG_BRIDGE_REFRESH).toString());
+ pollingJob = scheduler.scheduleWithFixedDelay(() -> {
+ logger.debug("Try to refresh meter data");
+ try {
+ updateMeter(cache.getValue());
+ } catch (RuntimeException r) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
+ }
+ }, 2, pollingPeriod, TimeUnit.MINUTES);
+ logger.debug("Refresh job scheduled to run every {} minutes for '{}'", pollingPeriod, getThing().getUID());
+ }
+
+ /**
+ * @return returns the auth token or null for error handling if the bridge was not found.
+ */
+ private @Nullable String getTokenFromBridge() {
+ Bridge b = this.getBridge();
+ if (b == null) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+ "Could not find bridge (pixometer config). Did you choose one?");
+ return null;
+ }
+
+ return new StringBuilder("Bearer ").append(((AccountHandler) b.getHandler()).getAuthToken()).toString();
+ }
+
+ @Override
+ public void dispose() {
+ if (pollingJob != null) {
+ pollingJob.cancel(true);
+ }
+ super.dispose();
+ }
+
+ @Override
+ public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
+ logger.debug("Bridge Status updated to {} for device: {}", bridgeStatusInfo.getStatus(), getThing().getUID());
+ if (bridgeStatusInfo.getStatus() != ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, bridgeStatusInfo.getDescription());
+ }
+ }
+
+ /**
+ * Requests the corresponding meter data and stores the meterId internally for later usage
+ *
+ * @param token The current active auth token
+ */
+ private void obtainMeterId() {
+ try {
+ String token = getTokenFromBridge();
+
+ if (token == null) {
+ throw new IOException(
+ "Auth token has not been delivered.\n API request can't get executed without authentication.");
+ }
+
+ String url = getApiString(API_METER_ENDPOINT);
+
+ Properties urlHeader = new Properties();
+ urlHeader.put("CONTENT-TYPE", "application/json");
+ urlHeader.put("Authorization", token);
+
+ String urlResponse = HttpUtil.executeUrl("GET", url, urlHeader, null, null, 2000);
+ JsonObject responseJson = (JsonObject) jsonParser.parse(urlResponse);
+
+ if (responseJson.has("meter_id")) {
+ setMeterID(responseJson.get("meter_id").toString());
+ 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 initialize Thing ( %s ). %s", this.getThing().getUID(),
+ e.getMessage());
+
+ logger.debug(errorMsg, e);
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMsg);
+ }
+ }
+
+ /**
+ * Checks if a channel is linked and redirects to the updateMeter method if link is existing
+ *
+ * @param channelUID the channel requested for refresh
+ * @param meterState a meterState instance with current values
+ */
+ private void updateMeter(ChannelUID channelUID, @Nullable MeterState meterState) throws IOException {
+ if (!isLinked(channelUID)) {
+ throw new IOException("Channel is not linked.");
+ }
+ updateMeter(meterState);
+ }
+
+ /**
+ * updates all corresponding channels
+ *
+ * @param token The current active access token
+ */
+ private void updateMeter(@Nullable MeterState meterState) {
+ try {
+ if (meterState == null) {
+ throw new IOException("Meter state has not been delivered to update method. Can't update channels.");
+ }
+
+ ThingTypeUID thingtype = getThing().getThingTypeUID();
+
+ if (THING_TYPE_ENERGYMETER.equals(thingtype)) {
+ QuantityType state = new QuantityType<>(meterState.getReadingValue(),
+ SmartHomeUnits.KILOWATT_HOUR);
+ updateState(CHANNEL_LAST_READING_VALUE, state);
+ }
+
+ if (thingtype.equals(THING_TYPE_GASMETER) || thingtype.equals(THING_TYPE_WATERMETER)) {
+ QuantityType state = new QuantityType<>(meterState.getReadingValue(), SIUnits.CUBIC_METRE);
+ updateState(CHANNEL_LAST_READING_VALUE, state);
+ }
+
+ updateState(CHANNEL_LAST_READING_DATE, meterState.getLastReadingDate());
+ updateState(CHANNEL_LAST_REFRESH_DATE, meterState.getLastRefreshTime());
+ } catch (IOException e) {
+ logger.debug("Exception while updating Meter {}: {}", getThing().getUID(), e.getMessage(), e);
+ }
+ }
+
+ private @Nullable MeterState refreshCache() {
+ try {
+ String url = getApiString(API_READINGS_ENDPOINT);
+
+ Properties urlHeader = new Properties();
+ urlHeader.put("CONTENT-TYPE", "application/json");
+ urlHeader.put("Authorization", getTokenFromBridge());
+
+ String urlResponse = HttpUtil.executeUrl("GET", url, urlHeader, null, null, 2000);
+
+ ReadingInstance latestReading = gson.fromJson(new JsonParser().parse(urlResponse), ReadingInstance.class);
+
+ return new MeterState(latestReading);
+ } catch (IOException e) {
+ logger.debug("Exception while refreshing cache for Meter {}: {}", getThing().getUID(), e.getMessage(), e);
+ return null;
+ }
+ }
+
+ /**
+ * Generates a url string based on the given api endpoint
+ *
+ * @param endpoint The choosen api endpoint
+ * @return The generated url string
+ */
+ private String getApiString(String endpoint) {
+ StringBuilder sb = new StringBuilder(API_BASE_URL);
+ sb.append(API_VERSION).append("/");
+
+ switch (endpoint) {
+ case API_METER_ENDPOINT:
+ sb.append(API_METER_ENDPOINT).append("/");
+ sb.append(this.getRessourceID()).append("/?");
+ break;
+ case API_READINGS_ENDPOINT:
+ sb.append(API_READINGS_ENDPOINT).append("/");
+ sb.append("?meter_ressource_id=").append(this.getRessourceID());
+ sb.append("&o=-reading_date").append("&");
+ break;
+ }
+
+ sb.append("format=json");
+ return sb.toString();
+ }
+
+ /**
+ * Getters and Setters
+ */
+
+ public String getRessourceID() {
+ return resourceID;
+ }
+
+ private void setRessourceID(String ressourceID) {
+ this.resourceID = ressourceID;
+ }
+
+ public String getMeterID() {
+ return meterID;
+ }
+
+ private void setMeterID(String meterID) {
+ this.meterID = meterID;
+ }
+}
diff --git a/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/PixometerBindingConstants.java b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/PixometerBindingConstants.java
new file mode 100644
index 0000000000000..d5bd8f2a5f4b1
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/PixometerBindingConstants.java
@@ -0,0 +1,61 @@
+/**
+ * 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.internal;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+
+/**
+ * The {@link PixometerBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Jerome Luckenbach - Initial contribution
+ */
+@NonNullByDefault
+public class PixometerBindingConstants {
+
+ private static final String BINDING_ID = "pixometer";
+
+ // Api base url
+ public static final String API_BASE_URL = "https://pixometer.io/api/";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID BRIDGE_THING_TYPE = new ThingTypeUID(BINDING_ID, "account");
+ public static final ThingTypeUID THING_TYPE_ENERGYMETER = new ThingTypeUID(BINDING_ID, "energymeter");
+ public static final ThingTypeUID THING_TYPE_GASMETER = new ThingTypeUID(BINDING_ID, "gasmeter");
+ public static final ThingTypeUID THING_TYPE_WATERMETER = new ThingTypeUID(BINDING_ID, "watermeter");
+
+ public static final Set BRIDGE_THING_TYPES_UIDS = Collections.singleton(BRIDGE_THING_TYPE);
+ public static final Set SUPPORTED_THING_TYPES_UIDS = Stream
+ .of(THING_TYPE_ENERGYMETER, THING_TYPE_GASMETER, THING_TYPE_WATERMETER).collect(Collectors.toSet());
+
+ // List of all Channel ids
+ public static final String CHANNEL_LAST_READING_VALUE = "last_reading_value";
+ public static final String CHANNEL_LAST_READING_DATE = "last_reading_date";
+ public static final String CHANNEL_LAST_REFRESH_DATE = "last_refresh_date";
+
+ // List of all config ids
+ public static final String CONFIG_BRIDGE_PASSWORD = "password";
+ public static final String CONFIG_BRIDGE_REFRESH = "refresh";
+ public static final String CONFIG_BRIDGE_USER = "user";
+
+ public static final String CONFIG_THING_RESSOURCE_ID = "resource_id";
+
+ // References for needed API identifiers
+ public static final String AUTH_TOKEN = "access_token";
+}
diff --git a/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/PixometerHandlerFactory.java b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/PixometerHandlerFactory.java
new file mode 100644
index 0000000000000..e7f76a92954bd
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/PixometerHandlerFactory.java
@@ -0,0 +1,72 @@
+/**
+ * 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.internal;
+
+import java.util.Set;
+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.smarthome.core.thing.Bridge;
+import org.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingTypeUID;
+import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory;
+import org.eclipse.smarthome.core.thing.binding.ThingHandler;
+import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory;
+import org.openhab.binding.pixometer.handler.AccountHandler;
+import org.openhab.binding.pixometer.handler.MeterHandler;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * The {@link PixometerHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Jerome Luckenbach - Initial contribution
+ */
+@NonNullByDefault
+@Component(configurationPid = "binding.pixometer", service = ThingHandlerFactory.class)
+public class PixometerHandlerFactory extends BaseThingHandlerFactory {
+
+ private static final Set SUPPORTED_THING_TYPES_UIDS = Stream
+ .of(PixometerBindingConstants.BRIDGE_THING_TYPES_UIDS, PixometerBindingConstants.SUPPORTED_THING_TYPES_UIDS)
+ .flatMap(Set::stream).collect(Collectors.toSet());
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (thingTypeUID.equals(PixometerBindingConstants.THING_TYPE_ENERGYMETER)) {
+ return new MeterHandler(thing);
+ }
+
+ if (thingTypeUID.equals(PixometerBindingConstants.THING_TYPE_GASMETER)) {
+ return new MeterHandler(thing);
+ }
+
+ if (thingTypeUID.equals(PixometerBindingConstants.THING_TYPE_WATERMETER)) {
+ return new MeterHandler(thing);
+ }
+
+ if (thingTypeUID.equals(PixometerBindingConstants.BRIDGE_THING_TYPE)) {
+ return new AccountHandler((Bridge) thing);
+ }
+
+ return null;
+ }
+}
diff --git a/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/Annotation.java b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/Annotation.java
new file mode 100644
index 0000000000000..0ae5428415343
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/Annotation.java
@@ -0,0 +1,73 @@
+/**
+ * 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.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link Annotation} class is the representing java model for the json result for a annotation from the pixometer
+ * api
+ *
+ * @author Jerome Luckenbach - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class Annotation {
+
+ private @NonNullByDefault({}) Integer id;
+ private @NonNullByDefault({}) Object rectangle;
+ private @NonNullByDefault({}) String meaning;
+ private @NonNullByDefault({}) String text;
+ private @NonNullByDefault({}) Integer image;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public Object getRectangle() {
+ return rectangle;
+ }
+
+ public void setRectangle(Object rectangle) {
+ this.rectangle = rectangle;
+ }
+
+ public String getMeaning() {
+ return meaning;
+ }
+
+ public void setMeaning(String meaning) {
+ this.meaning = meaning;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+ public Integer getImage() {
+ return image;
+ }
+
+ public void setImage(Integer image) {
+ this.image = image;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/ImageMeta.java b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/ImageMeta.java
new file mode 100644
index 0000000000000..b7462077867af
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/ImageMeta.java
@@ -0,0 +1,147 @@
+/**
+ * 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.internal.config;
+
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ImageMeta} class is the representing java model for the json result for Image Meta Data from the pixometer
+ * api
+ *
+ * @author Jerome Luckenbach - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class ImageMeta {
+
+ private @NonNullByDefault({}) Integer id;
+ private @NonNullByDefault({}) List annotations = null;
+ private @NonNullByDefault({}) String image;
+ private @NonNullByDefault({}) String imageDownload;
+ private @NonNullByDefault({}) String cameraModel;
+ private @NonNullByDefault({}) Boolean flash;
+ private @NonNullByDefault({}) Integer frameNumber;
+ private @NonNullByDefault({}) Double secondsSinceDetection;
+ private @NonNullByDefault({}) Double secondsSinceStart;
+ private @NonNullByDefault({}) Double lat;
+ private @NonNullByDefault({}) Double lng;
+ private @NonNullByDefault({}) String osVersion;
+ private @NonNullByDefault({}) String pixolusVersion;
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public List getAnnotations() {
+ return annotations;
+ }
+
+ public void setAnnotations(List annotations) {
+ this.annotations = annotations;
+ }
+
+ public String getImage() {
+ return image;
+ }
+
+ public void setImage(String image) {
+ this.image = image;
+ }
+
+ public String getImageDownload() {
+ return imageDownload;
+ }
+
+ public void setImageDownload(String imageDownload) {
+ this.imageDownload = imageDownload;
+ }
+
+ public String getCameraModel() {
+ return cameraModel;
+ }
+
+ public void setCameraModel(String cameraModel) {
+ this.cameraModel = cameraModel;
+ }
+
+ public Boolean getFlash() {
+ return flash;
+ }
+
+ public void setFlash(Boolean flash) {
+ this.flash = flash;
+ }
+
+ public Integer getFrameNumber() {
+ return frameNumber;
+ }
+
+ public void setFrameNumber(Integer frameNumber) {
+ this.frameNumber = frameNumber;
+ }
+
+ public Double getSecondsSinceDetection() {
+ return secondsSinceDetection;
+ }
+
+ public void setSecondsSinceDetection(Double secondsSinceDetection) {
+ this.secondsSinceDetection = secondsSinceDetection;
+ }
+
+ public Double getSecondsSinceStart() {
+ return secondsSinceStart;
+ }
+
+ public void setSecondsSinceStart(Double secondsSinceStart) {
+ this.secondsSinceStart = secondsSinceStart;
+ }
+
+ public Double getLat() {
+ return lat;
+ }
+
+ public void setLat(Double lat) {
+ this.lat = lat;
+ }
+
+ public Double getLng() {
+ return lng;
+ }
+
+ public void setLng(Double lng) {
+ this.lng = lng;
+ }
+
+ public String getOsVersion() {
+ return osVersion;
+ }
+
+ public void setOsVersion(String osVersion) {
+ this.osVersion = osVersion;
+ }
+
+ public String getPixolusVersion() {
+ return pixolusVersion;
+ }
+
+ public void setPixolusVersion(String pixolusVersion) {
+ this.pixolusVersion = pixolusVersion;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/MeterInstance.java b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/MeterInstance.java
new file mode 100644
index 0000000000000..cd96dc4b7493c
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/MeterInstance.java
@@ -0,0 +1,207 @@
+/**
+ * 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.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link MeterInstance} class is the representing java model for the json result for a meter from the pixometer
+ * api
+ *
+ * @author Jerome Luckenbach - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class MeterInstance {
+
+ private @NonNullByDefault({}) String url;
+ private @NonNullByDefault({}) String owner;
+ private @NonNullByDefault({}) String changedHash;
+ private @NonNullByDefault({}) String created;
+ private @NonNullByDefault({}) String modified;
+ private @NonNullByDefault({}) String appearance;
+ private @NonNullByDefault({}) Integer fractionDigits;
+ private @NonNullByDefault({}) Boolean isDoubleTariff;
+ private @NonNullByDefault({}) String locationInBuilding;
+ private @NonNullByDefault({}) String meterId;
+ private @NonNullByDefault({}) String physicalMedium;
+ private @NonNullByDefault({}) String physicalUnit;
+ private @NonNullByDefault({}) Integer integerDigits;
+ private @NonNullByDefault({}) String registerOrder;
+ private @NonNullByDefault({}) Object city;
+ private @NonNullByDefault({}) Object zipCode;
+ private @NonNullByDefault({}) Object address;
+ private @NonNullByDefault({}) Object description;
+ private @NonNullByDefault({}) Object label;
+ private @NonNullByDefault({}) Integer resourceId;
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getOwner() {
+ return owner;
+ }
+
+ public void setOwner(String owner) {
+ this.owner = owner;
+ }
+
+ public String getChangedHash() {
+ return changedHash;
+ }
+
+ public void setChangedHash(String changedHash) {
+ this.changedHash = changedHash;
+ }
+
+ public String getCreated() {
+ return created;
+ }
+
+ public void setCreated(String created) {
+ this.created = created;
+ }
+
+ public String getModified() {
+ return modified;
+ }
+
+ public void setModified(String modified) {
+ this.modified = modified;
+ }
+
+ public String getAppearance() {
+ return appearance;
+ }
+
+ public void setAppearance(String appearance) {
+ this.appearance = appearance;
+ }
+
+ public Integer getFractionDigits() {
+ return fractionDigits;
+ }
+
+ public void setFractionDigits(Integer fractionDigits) {
+ this.fractionDigits = fractionDigits;
+ }
+
+ public Boolean getIsDoubleTariff() {
+ return isDoubleTariff;
+ }
+
+ public void setIsDoubleTariff(Boolean isDoubleTariff) {
+ this.isDoubleTariff = isDoubleTariff;
+ }
+
+ public String getLocationInBuilding() {
+ return locationInBuilding;
+ }
+
+ public void setLocationInBuilding(String locationInBuilding) {
+ this.locationInBuilding = locationInBuilding;
+ }
+
+ public String getMeterId() {
+ return meterId;
+ }
+
+ public void setMeterId(String meterId) {
+ this.meterId = meterId;
+ }
+
+ public String getPhysicalMedium() {
+ return physicalMedium;
+ }
+
+ public void setPhysicalMedium(String physicalMedium) {
+ this.physicalMedium = physicalMedium;
+ }
+
+ public String getPhysicalUnit() {
+ return physicalUnit;
+ }
+
+ public void setPhysicalUnit(String physicalUnit) {
+ this.physicalUnit = physicalUnit;
+ }
+
+ public Integer getIntegerDigits() {
+ return integerDigits;
+ }
+
+ public void setIntegerDigits(Integer integerDigits) {
+ this.integerDigits = integerDigits;
+ }
+
+ public String getRegisterOrder() {
+ return registerOrder;
+ }
+
+ public void setRegisterOrder(String registerOrder) {
+ this.registerOrder = registerOrder;
+ }
+
+ public Object getCity() {
+ return city;
+ }
+
+ public void setCity(Object city) {
+ this.city = city;
+ }
+
+ public Object getZipCode() {
+ return zipCode;
+ }
+
+ public void setZipCode(Object zipCode) {
+ this.zipCode = zipCode;
+ }
+
+ public Object getAddress() {
+ return address;
+ }
+
+ public void setAddress(Object address) {
+ this.address = address;
+ }
+
+ public Object getDescription() {
+ return description;
+ }
+
+ public void setDescription(Object description) {
+ this.description = description;
+ }
+
+ public Object getLabel() {
+ return label;
+ }
+
+ public void setLabel(Object label) {
+ this.label = label;
+ }
+
+ public Integer getResourceId() {
+ return resourceId;
+ }
+
+ public void setResourceId(Integer resourceId) {
+ this.resourceId = resourceId;
+ }
+}
diff --git a/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/PixometerAccountConfiguration.java b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/PixometerAccountConfiguration.java
new file mode 100644
index 0000000000000..5c4d312992834
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/PixometerAccountConfiguration.java
@@ -0,0 +1,41 @@
+/**
+ * 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.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Data class representing the user configurable settings of the api
+ *
+ * @author Jerome Luckenbach - Initial contribution
+ */
+@NonNullByDefault
+public class PixometerAccountConfiguration {
+
+ /**
+ * The configured user name
+ */
+ public @NonNullByDefault({}) String user;
+
+ /**
+ * The configured password
+ */
+ public @NonNullByDefault({}) String password;
+
+ /**
+ * Configured refresh rate
+ */
+ public int refresh;
+
+}
diff --git a/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/PixometerMeterConfiguration.java b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/PixometerMeterConfiguration.java
new file mode 100644
index 0000000000000..52bf00577ce48
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/PixometerMeterConfiguration.java
@@ -0,0 +1,31 @@
+/**
+ * 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.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Data class representing the user configurable settings of a meter thing
+ *
+ * @author Jerome Luckenbach - Initial contribution
+ */
+@NonNullByDefault
+public class PixometerMeterConfiguration {
+
+ /**
+ * The resourceId of the current meter
+ */
+ public @NonNullByDefault({}) String resourceId;
+
+}
diff --git a/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/ReadingInstance.java b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/ReadingInstance.java
new file mode 100644
index 0000000000000..36b2e597cf87a
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/config/ReadingInstance.java
@@ -0,0 +1,93 @@
+/**
+ * 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.internal.config;
+
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link ReadingInstance} class is the representing java model for the json result for a reading from the pixometer
+ * api
+ *
+ * @author Jerome Luckenbach - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class ReadingInstance {
+
+ private @NonNullByDefault({}) String url;
+ private @NonNullByDefault({}) String appliedMethod;
+ private @NonNullByDefault({}) ZonedDateTime readingDate;
+ private double value;
+ private double valueSecondTariff;
+ private int providedFractionDigits;
+ private int providedFractionDigitsSecondTariff;
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getAppliedMethod() {
+ return appliedMethod;
+ }
+
+ public void setAppliedMethod(String appliedMethod) {
+ this.appliedMethod = appliedMethod;
+ }
+
+ public ZonedDateTime getReadingDate() {
+ return readingDate;
+ }
+
+ public void setReadingDate(ZonedDateTime readingDate) {
+ this.readingDate = readingDate;
+ }
+
+ public double getValue() {
+ return value;
+ }
+
+ public void setValue(double value) {
+ this.value = value;
+ }
+
+ public double getValueSecondTariff() {
+ return valueSecondTariff;
+ }
+
+ public void setValueSecondTariff(double valueSecondTariff) {
+ this.valueSecondTariff = valueSecondTariff;
+ }
+
+ public int getProvidedFractionDigits() {
+ return providedFractionDigits;
+ }
+
+ public void setProvidedFractionDigits(int provided_fraction_digits) {
+ this.providedFractionDigits = provided_fraction_digits;
+ }
+
+ public int getProvidedFractionDigitsSecondTariff() {
+ return providedFractionDigitsSecondTariff;
+ }
+
+ public void setProvidedFractionDigitsSecondTariff(int provided_fraction_digits_second_tariff) {
+ this.providedFractionDigitsSecondTariff = provided_fraction_digits_second_tariff;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/data/MeterState.java b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/data/MeterState.java
new file mode 100644
index 0000000000000..07500cefd0f87
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/data/MeterState.java
@@ -0,0 +1,65 @@
+/**
+ * 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.internal.data;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.smarthome.core.library.types.DateTimeType;
+import org.openhab.binding.pixometer.internal.config.ReadingInstance;
+
+/**
+ * Abstract data class to store shared/common meter state values
+ *
+ * @author Jerome Luckenbach - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class MeterState {
+
+ private final DateTimeType lastReadingDate;
+ private final DateTimeType lastRefreshTime;
+ private final double readingValue;
+
+ /**
+ * Initialize times from the given timestamps
+ *
+ * @param lastReadingDate time of last reading as ZonedDateTime
+ * @param lastRefreshTime time of last refresh as ZonedDateTime
+ */
+ public MeterState(ReadingInstance reading) {
+ this.readingValue = reading.getValue();
+ this.lastReadingDate = new DateTimeType(reading.getReadingDate());
+ this.lastRefreshTime = new DateTimeType();
+ }
+
+ /**
+ * @return returns the current reading value
+ */
+ public double getReadingValue() {
+ return readingValue;
+ }
+
+ /**
+ * @return returns the last time that the meter has been read into pixometer
+ */
+ public DateTimeType getLastReadingDate() {
+ return lastReadingDate;
+ }
+
+ /**
+ * @return returns the last time, the item has been refreshed
+ */
+ public DateTimeType getLastRefreshTime() {
+ return lastRefreshTime;
+ }
+
+}
diff --git a/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/serializer/CustomReadingInstanceDeserializer.java b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/serializer/CustomReadingInstanceDeserializer.java
new file mode 100644
index 0000000000000..c51cf3d65340f
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/java/org/openhab/binding/pixometer/internal/serializer/CustomReadingInstanceDeserializer.java
@@ -0,0 +1,110 @@
+/**
+ * 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.internal.serializer;
+
+import java.lang.reflect.Type;
+import java.time.Instant;
+import java.time.ZonedDateTime;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.pixometer.internal.config.ReadingInstance;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+
+/**
+ * Custom Deserializer for meter reading api responses
+ *
+ * @author Jerome Luckenbach - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class CustomReadingInstanceDeserializer implements JsonDeserializer {
+
+ private static final String COUNT = "count";
+ private static final String RESULTS = "results";
+ private static final String URL = "url";
+ private static final String APPLIED_METHOD = "applied_method";
+ private static final String READING_DATE = "reading_date";
+ private static final String VALUE = "value";
+ private static final String VALUE_SECOND_TARIFF = "value_second_tariff";
+ private static final String PROVIDED_FRACTION_DIGITS = "provided_fraction_digits";
+ private static final String PROVIDED_FRACTION_DIGITS_SECOND_TARIFF = "provided_fraction_digits_second_tariff";
+
+ @Override
+ @NonNullByDefault({})
+ public ReadingInstance deserialize(final JsonElement json, final Type typeOfT,
+ final JsonDeserializationContext context) throws JsonParseException {
+ final JsonObject jsonObject = json.getAsJsonObject();
+
+ if (!jsonObject.has(COUNT)) {
+ throw new JsonParseException("Invalid Json Response");
+ }
+
+ ReadingInstance result = new ReadingInstance();
+ int resultCount = Integer.parseInt(jsonObject.get(COUNT).toString());
+
+ // No readings provided yet
+ if (resultCount < 1) {
+ result.setReadingDate(ZonedDateTime.from(Instant.EPOCH));
+ result.setValue(0);
+ }
+
+ // Fist result is the last reading instance
+ JsonObject latestReading = jsonObject.getAsJsonArray(RESULTS).get(0).getAsJsonObject();
+
+ result.setUrl(getStringFromJson(latestReading, URL));
+ result.setAppliedMethod(getStringFromJson(latestReading, APPLIED_METHOD));
+ result.setReadingDate(ZonedDateTime.parse(getStringFromJson(latestReading, READING_DATE)));
+ result.setValue(Double.parseDouble(getStringFromJson(latestReading, VALUE)));
+
+ // Not all meters provide useful data for second tariff and fraction digits , so zero should be used in case of
+ // a null value.
+ String secondTariffValue = getStringFromJson(latestReading, VALUE_SECOND_TARIFF);
+ result.setValueSecondTariff(
+ checkStringForNullValues(secondTariffValue) ? 0 : Double.parseDouble(secondTariffValue));
+
+ String providedFractionDigits = getStringFromJson(latestReading, PROVIDED_FRACTION_DIGITS);
+ result.setProvidedFractionDigits(
+ checkStringForNullValues(providedFractionDigits) ? 0 : Integer.parseInt(providedFractionDigits));
+
+ String secondprovidedFractionDigits = getStringFromJson(latestReading, PROVIDED_FRACTION_DIGITS_SECOND_TARIFF);
+ result.setProvidedFractionDigitsSecondTariff(checkStringForNullValues(secondprovidedFractionDigits) ? 0
+ : Integer.parseInt(secondprovidedFractionDigits));
+
+ return result;
+ }
+
+ /**
+ * Returns the node value and removes possible added quotation marks, which would lead to parse errors.
+ *
+ * @param data The Json source to get the string from
+ * @param key The key for the wanted Json Node
+ * @return The wanted string without unnecessary quotation marks
+ */
+ private String getStringFromJson(JsonObject data, String key) {
+ return data.get(key).toString().replaceAll("\"", "");
+ }
+
+ /**
+ * @param s the striong to check
+ * @return returns true if null values have been found, false otherwise
+ */
+ private boolean checkStringForNullValues(String s) {
+ return (s == null || s.isEmpty() || s.equals("null"));
+ }
+
+}
diff --git a/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/binding/binding.xml
new file mode 100644
index 0000000000000..a8e4c0d11a3c2
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/binding/binding.xml
@@ -0,0 +1,10 @@
+
+
+
+ Pixometer Binding
+ This binding integrates meter data through pixometer.
+ Jerome Luckenbach
+
+
diff --git a/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/i18n/pixometer_de_DE.properties b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/i18n/pixometer_de_DE.properties
new file mode 100644
index 0000000000000..1492ef07fa7ab
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/i18n/pixometer_de_DE.properties
@@ -0,0 +1,44 @@
+# binding
+binding.pixometer.name = Pixometer Binding
+binding.pixometer.description = Mit dem Pixometer Binding ist es möglich über die Pixometer.io API Daten und Messwerte von den dort erfassten Zählern abzurufen.
+
+# thing types
+thing-type.pixometer.account.label = Pixometer Account
+thing-type.pixometer.account.description = Stellt die Verbindung zur API für die eingerichteten Zähler bereit.
+
+thing-type.config.pixometer.account.user.label = Benutzername
+thing-type.config.pixometer.account.user.description = Der Pixometer Benutzername
+
+thing-type.config.pixometer.account.password.label = Password
+thing-type.config.pixometer.account.password.description = Das Pixometer Passwort
+
+thing-type.config.pixometer.account.refresh.label = Aktualisierungsintervall
+thing-type.config.pixometer.account.refresh.description = Das Intervall in dem ein Zähler aktualisiert wird (in Minuten, Minimum 60)
+
+thing-type.pixometer.energymeter.label = Stromzähler
+thing-type.pixometer.energymeter.description = Stellt die Informationen und Messwerte zu einem Stromzähler bereit.
+
+thing-type.config.pixometer.energymeter.resource_id.label = Zähler Ressourcen ID
+thing-type.config.pixometer.energymeter.resource_id.description = Unter dieser ID wird der Zähler im Pixometer Account geführt. Sie ist in der Pixometer Homepage in der Adressleiste des Browsers beim Bearbeiten des Zählers ersichtlich.
+
+thing-type.pixometer.gasmeter.label = Gaszähler
+thing-type.pixometer.gasmeter.description = Stellt die Informationen und Messwerte zu einem Gaszähler bereit.
+
+thing-type.config.pixometer.gasmeter.resource_id.label = Zähler Ressourcen ID
+thing-type.config.pixometer.gasmeter.resource_id.description = Unter dieser ID wird der Zähler im Pixometer Account geführt. Sie ist in der Pixometer Homepage in der Adressleiste des Browsers beim Bearbeiten des Zählers ersichtlich.
+
+thing-type.pixometer.watermeter.label = Wasserzähler
+thing-type.pixometer.watermeter.description = Stellt die Informationen und Messwerte zu einem Wasserzähler bereit.
+
+thing-type.config.pixometer.watermeter.resource_id.label = Zähler Ressourcen ID
+thing-type.config.pixometer.watermeter.resource_id.description = Unter dieser ID wird der Zähler im Pixometer Account geführt. Sie ist in der Pixometer Homepage in der Adressleiste des Browsers beim Bearbeiten des Zählers ersichtlich.
+
+# channel types
+channel-type.pixometer.last_reading_value.label = Zählerstand
+channel-type.pixometer.last_reading_value.description = Der zuletzt von der API abgefragte Zählerstand für diesen Zähler.
+
+channel-type.pixometer.last_reading_date.label = Ablesezeitpunkt
+channel-type.pixometer.last_reading_date.description = Der Zeitpunkt der zuletzt abgefragten Ablesung für diesem Zähler.
+
+channel-type.pixometer.last_refresh_date.label = Aktualisierungszeitpunkt
+channel-type.pixometer.last_refresh_date.description = Der Zeitpunkt an dem dieser Zähler zuletzt vom Binding in der API abgefragt wurde.
diff --git a/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/i18n/pixometer_en_US.properties b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/i18n/pixometer_en_US.properties
new file mode 100644
index 0000000000000..829c1e1cf310e
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/i18n/pixometer_en_US.properties
@@ -0,0 +1,44 @@
+# binding
+binding.pixometer.name = Pixometer Binding
+binding.pixometer.description = This binding can connect to the Pixometer.io API and accesses meters and measurement data from the connected account.
+
+# thing types
+thing-type.pixometer.account.label = Pixometer Account
+thing-type.pixometer.account.description =
+
+thing-type.config.pixometer.account.user.label = Username
+thing-type.config.pixometer.account.user.description = The Pixometer username
+
+thing-type.config.pixometer.account.password.label = Password
+thing-type.config.pixometer.account.password.description = The Pixometer password
+
+thing-type.config.pixometer.account.refresh.label = Refresh Interval
+thing-type.config.pixometer.account.refresh.description = The interval on which the API is refreshed. (at least 60 minutes)
+
+thing-type.pixometer.energymeter.label = Energymeter
+thing-type.pixometer.energymeter.description = An energymeter thing.
+
+thing-type.config.pixometer.energymeter.resource_id.label = Meter Ressource ID
+thing-type.config.pixometer.energymeter.resource_id.description = 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"
+
+thing-type.pixometer.gasmeter.label = Gasmeter
+thing-type.pixometer.gasmeter.description = A gas meter.
+
+thing-type.config.pixometer.gasmeter.resource_id.label = Meter Tressource ID
+thing-type.config.pixometer.gasmeter.resource_id.description = 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"
+
+thing-type.pixometer.watermeter.label = Watermeter
+thing-type.pixometer.watermeter.description = A water meter.
+
+thing-type.config.pixometer.watermeter.resource_id.label = Meter Ressource ID
+thing-type.config.pixometer.watermeter.resource_id.description = 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"
+
+# channel types
+channel-type.pixometer.last_reading_value.label = Reading Value
+channel-type.pixometer.last_reading_value.description = Value of the last reading that has been made with pixometer.
+
+channel-type.pixometer.last_reading_date.label = Reading Date
+channel-type.pixometer.last_reading_date.description = Date of the last reading that has been made with pixometer.
+
+channel-type.pixometer.last_refresh_date.label = Refresh Date
+channel-type.pixometer.last_refresh_date.description = Date of the last time the Api has been refreshed.
diff --git a/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/account.xml b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/account.xml
new file mode 100644
index 0000000000000..e363e78c13ed7
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/account.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+ This Bridge handles your Pixometer account.
+
+
+
+ Your Pixometer Username.
+
+
+
+ password
+
+ Your Pixometer Password.
+
+
+
+
+ Sets the refresh time. Minimum is 60 Minutes.
+ 240
+
+
+
+
diff --git a/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/channels.xml b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/channels.xml
new file mode 100644
index 0000000000000..2f98ccc5a544b
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/channels.xml
@@ -0,0 +1,18 @@
+
+
+
+ DateTime
+
+ The last time that the current meter has been read.
+
+
+
+ DateTime
+
+ The last time that the thing has been refreshed.
+
+
+
diff --git a/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/energymeter.xml b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/energymeter.xml
new file mode 100644
index 0000000000000..cc858a83471b5
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/energymeter.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+ A specific energy meter.
+
+
+
+
+
+
+
+
+ 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"
+
+
+
+
+ Number:Energy
+
+ The last value that has been read for this meter.
+
+
+
diff --git a/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/gasmeter.xml b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/gasmeter.xml
new file mode 100644
index 0000000000000..9c8347ee73c26
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/gasmeter.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+ A specific gas meter.
+
+
+
+
+
+
+
+
+ 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"
+
+
+
+
+ Number:Volume
+
+ The last value that has been read for this meter.
+
+
+
diff --git a/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/watermeter.xml b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/watermeter.xml
new file mode 100644
index 0000000000000..b26c4d728b43c
--- /dev/null
+++ b/bundles/org.openhab.binding.pixometer/src/main/resources/ESH-INF/thing/watermeter.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+ A specific water meter.
+
+
+
+
+
+
+
+
+ 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"
+
+
+
+
+ Number:Volume
+
+ The last value that has been read for this meter.
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index 382cb07d874ec..da708e192b53d 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -168,6 +168,7 @@
org.openhab.binding.pentair
org.openhab.binding.phc
org.openhab.binding.pioneeravr
+ org.openhab.binding.pixometer
org.openhab.binding.pjlinkdevice
org.openhab.binding.plclogo
org.openhab.binding.plugwise