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