]> git.basschouten.com Git - openhab-addons.git/commitdiff
[amberelectric] Initial contribution (#16850)
authorPaul Smedley <paul@smedley.id.au>
Sat, 15 Jun 2024 09:40:01 +0000 (19:10 +0930)
committerGitHub <noreply@github.com>
Sat, 15 Jun 2024 09:40:01 +0000 (11:40 +0200)
Signed-off-by: Paul Smedley <paul@smedley.id.au>
18 files changed:
CODEOWNERS
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.amberelectric/NOTICE [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/README.md [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/pom.xml [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricBindingConstants.java [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricCommunicationException.java [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricConfiguration.java [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricHandlerFactory.java [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricWebTargets.java [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/api/CurrentPrices.java [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/api/Sites.java [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/src/main/resources/OH-INF/addon/addon.xml [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/src/main/resources/OH-INF/i18n/amberelectric.properties [new file with mode: 0644]
bundles/org.openhab.binding.amberelectric/src/main/resources/OH-INF/thing/thing-types.xml [new file with mode: 0644]
bundles/pom.xml

index 0511f63f7dc77d34d4f4bd6c85f0751d48ed6d12..7cb21544f805192c6a80bebe1b8fba2a498569f8 100755 (executable)
@@ -22,6 +22,7 @@
 /bundles/org.openhab.binding.allplay/ @dominicdesu
 /bundles/org.openhab.binding.amazondashbutton/ @openhab/add-ons-maintainers
 /bundles/org.openhab.binding.amazonechocontrol/ @mgeramb
+/bundles/org.openhab.binding.amberelectric/ @psmedley
 /bundles/org.openhab.binding.ambientweather/ @mhilbush
 /bundles/org.openhab.binding.amplipi/ @kaikreuzer
 /bundles/org.openhab.binding.androiddebugbridge/ @GiviMAD
index f97b4b7010c7ef55f1f892dfe369d28d93e76c03..c1e15edf9485de923a88760a5f9678df64516f9d 100644 (file)
       <artifactId>org.openhab.binding.amazonechocontrol</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.openhab.addons.bundles</groupId>
+      <artifactId>org.openhab.binding.amberelectric</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.ambientweather</artifactId>
diff --git a/bundles/org.openhab.binding.amberelectric/NOTICE b/bundles/org.openhab.binding.amberelectric/NOTICE
new file mode 100644 (file)
index 0000000..38d625e
--- /dev/null
@@ -0,0 +1,13 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
diff --git a/bundles/org.openhab.binding.amberelectric/README.md b/bundles/org.openhab.binding.amberelectric/README.md
new file mode 100644 (file)
index 0000000..f856edd
--- /dev/null
@@ -0,0 +1,69 @@
+# Amber Electric Binding
+
+A binding that supports the Australian energy retailer Amber's API (<https://www.amber.com.au/>) and provides data on the current pricing for buying and selling power, as well as the current level of renewables in the NEM.
+
+## Supported Things
+
+- `service` Amber Electric API 
+
+## Discovery
+
+The binding does not support auto discovery.
+
+## Thing Configuration
+
+As a minimum, the IP address is needed:
+
+- `apiKey` - The API key from the 'Developer' section of <https://apps.amber.com.au>
+- 'nmi' optional -  the NMI for your property. Required if you have multiple properties with Amber
+- 'refresh' the refresh rate for querying the API.
+
+## Channels
+
+| channel id             | type                 | description                                                                     |
+|------------------------|----------------------|---------------------------------------------------------------------------------|
+| electricity-price      | Number:EnergyPrice   | Current price to import power from the grid
+| controlled-load-price  | Number:EnergyPrice   | Current price to import power for Controlled Load
+| feed-in-price          | Number:EnergyPrice   | Current price to export power to the grid
+| electricity-status     | String               | Current price status of grid import 
+| controlled-load-status | String               | Current price status of controlled load import
+| feed-in-status         | String               | Current price status of Feed-In
+| nem-time               | String               | NEM time of last pricing update
+| renewables             | Number:Dimensionless | Current level of renewables in the grid
+| spike                  | Switch               | Report if the grid has a current price spike
+
+## Full Example
+
+### `amberelectric.things`:
+
+```java
+amberelectric:service:AmberElectric [ apiKey="psk_xxxxxxxxxxxxxxxxxxxx" ]
+```
+
+### `amberelectric.items`:
+
+```java
+Number:EnergyPrice AmberElectric_ElectricityPrice { channel="amberelectric:service:AmberElectric:electricity-price" }
+Number:EnergyPrice AmberElectric_ControlledLoadPrice { channel="amberelectric:service:AmberElectric:controlled-load-price" }
+Number:EnergyPrice AmberElectric_FeedInPrice { channel="amberelectric:service:AmberElectric:feed-in-price" }
+String AmberElectric_ElectricityStatus { channel="amberelectric:service:AmberElectric:electricity-status" }
+String AmberElectric_ControlledLoadStatus { channel="amberelectric:service:AmberElectric:controlled-load-status" }
+String AmberElectric_FeedInStatus { channel="amberelectric:service:AmberElectric:feed-in-status" }
+String AmberElectric_nemtime { channel="amberelectric:service:AmberElectric:nem-time" }
+Number AmberElectric_Renewables { channel="amberelectric:service:AmberElectric:renewables" }
+Switch AmberElectric_Spike { channel="amberelectric:service:AmberElectric:spike" }
+```
+    
+### `amberelectric.sitemap`:
+
+```perl
+Text item=AmberElectric_ElectricityPrice label="Electricity Price"
+Text item=AmberElectric_ControlledLoadPrice label="Controlled Load Price"
+Text item=AmberElectric_FeedInPrice label="Feed-In Price"
+Text item=AmberElectric_ElectricityStatus label="Electricity Price Status"
+Text item=AmberElectric_ControlledLoadStatus label="Controlled Load Price Status"
+Text item=AmberElectric_FeedInStatus label="Feed-In Price Status"
+Text item=AmberElectric_nemtime label="Current time of NEM pricing"
+Text item=AmberElectric_Renewables label="Renewables Level"
+Switch item=AmberElectric_Spike  label="Spike Status"
+```
diff --git a/bundles/org.openhab.binding.amberelectric/pom.xml b/bundles/org.openhab.binding.amberelectric/pom.xml
new file mode 100644 (file)
index 0000000..cdb9cdf
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.openhab.addons.bundles</groupId>
+    <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+    <version>4.2.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>org.openhab.binding.amberelectric</artifactId>
+
+  <name>openHAB Add-ons :: Bundles :: Amber Electric Binding</name>
+
+</project>
diff --git a/bundles/org.openhab.binding.amberelectric/src/main/feature/feature.xml b/bundles/org.openhab.binding.amberelectric/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..233fe03
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.amberelectric-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
+       <repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
+
+       <feature name="openhab-binding-amberelectric" description="Amber Electric Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.amberelectric/${project.version}</bundle>
+       </feature>
+</features>
diff --git a/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricBindingConstants.java b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricBindingConstants.java
new file mode 100644 (file)
index 0000000..004bdc7
--- /dev/null
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2010-2024 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.amberelectric.internal;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link AmberElectricBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Paul Smedley - Initial contribution
+ */
+@NonNullByDefault
+public class AmberElectricBindingConstants {
+
+    private static final String BINDING_ID = "amberelectric";
+
+    // List of all Thing Type UIDs
+    public static final ThingTypeUID AMBERELECTRIC_THING = new ThingTypeUID(BINDING_ID, "service");
+
+    // List of all Channel ids
+    public static final String CHANNEL_ELECTRICITY_PRICE = "electricity-price";
+    public static final String CHANNEL_CONTROLLED_LOAD_PRICE = "controlled-load-price";
+    public static final String CHANNEL_FEED_IN_PRICE = "feed-in-price";
+    public static final String CHANNEL_ELECTRICITY_STATUS = "electricity-status";
+    public static final String CHANNEL_CONTROLLED_LOAD_STATUS = "controlled-load-status";
+    public static final String CHANNEL_FEED_IN_STATUS = "feed-in-status";
+    public static final String CHANNEL_NEM_TIME = "nem-time";
+    public static final String CHANNEL_RENEWABLES = "renewables";
+    public static final String CHANNEL_SPIKE = "spike";
+
+    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(AMBERELECTRIC_THING);
+}
diff --git a/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricCommunicationException.java b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricCommunicationException.java
new file mode 100644 (file)
index 0000000..d8a9c45
--- /dev/null
@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2010-2024 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.amberelectric.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Exception for when an unexpected response is received from the AmberAPI.
+ *
+ * @author Paul Smedley - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class AmberElectricCommunicationException extends Exception {
+    private static final long serialVersionUID = 529232811860854017L;
+
+    public AmberElectricCommunicationException(String message) {
+        super(message);
+    }
+
+    public AmberElectricCommunicationException(Throwable ex) {
+        super(ex);
+    }
+
+    public AmberElectricCommunicationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricConfiguration.java b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricConfiguration.java
new file mode 100644 (file)
index 0000000..eaf1157
--- /dev/null
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2010-2024 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.amberelectric.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The AmberElectricConfiguration class contains fields mapping thing configuration parameters.
+ *
+ * @author Paul Smedley - Initial contribution
+ */
+@NonNullByDefault
+public class AmberElectricConfiguration {
+    public String apiKey = "";
+    public String nmi = "";
+    public long refresh = 60;
+}
diff --git a/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricHandler.java b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricHandler.java
new file mode 100644 (file)
index 0000000..aa91c4b
--- /dev/null
@@ -0,0 +1,168 @@
+/**
+ * Copyright (c) 2010-2024 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.amberelectric.internal;
+
+import java.io.IOException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import javax.measure.Unit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.amberelectric.internal.api.CurrentPrices;
+import org.openhab.binding.amberelectric.internal.api.Sites;
+import org.openhab.core.config.core.Configuration;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.CurrencyUnits;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link AmberElectricHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Paul Smedley - Initial contribution
+ */
+@NonNullByDefault
+public class AmberElectricHandler extends BaseThingHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(AmberElectricHandler.class);
+
+    private long refreshInterval;
+    private String apiKey = "";
+    private String nmi = "";
+    private String siteID = "";
+
+    private @NonNullByDefault({}) AmberElectricConfiguration config;
+    private @NonNullByDefault({}) AmberElectricWebTargets webTargets;
+    private @Nullable ScheduledFuture<?> pollFuture;
+
+    public AmberElectricHandler(Thing thing) {
+        super(thing);
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        logger.warn("This binding is read only");
+    }
+
+    @Override
+    public void initialize() {
+        config = getConfigAs(AmberElectricConfiguration.class);
+        if (config.apiKey.isBlank()) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
+                    "@text/offline.conf-error.no-api-key");
+            return;
+        }
+
+        webTargets = new AmberElectricWebTargets();
+        updateStatus(ThingStatus.UNKNOWN);
+        refreshInterval = config.refresh;
+        nmi = config.nmi;
+        apiKey = config.apiKey;
+
+        schedulePoll();
+    }
+
+    @Override
+    public void dispose() {
+        super.dispose();
+        stopPoll();
+    }
+
+    private void schedulePoll() {
+        logger.debug("Scheduling poll every {} s", refreshInterval);
+        this.pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, refreshInterval, TimeUnit.SECONDS);
+    }
+
+    private void poll() {
+        try {
+            logger.debug("Polling for state");
+            pollStatus();
+        } catch (IOException e) {
+            logger.debug("Could not connect to AmberAPI", e);
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+        } catch (RuntimeException e) {
+            logger.warn("Unexpected error connecting to AmberAPI", e);
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+        }
+    }
+
+    private void stopPoll() {
+        final Future<?> future = pollFuture;
+        if (future != null) {
+            future.cancel(true);
+            pollFuture = null;
+        }
+    }
+
+    private void pollStatus() throws IOException {
+        try {
+            if (siteID.isEmpty()) {
+                Sites sites = webTargets.getSites(apiKey, nmi);
+                // add error handling
+                siteID = sites.siteid;
+                Configuration configuration = editConfiguration();
+                configuration.put("nmi", sites.nmi);
+                updateConfiguration(configuration);
+                logger.debug("Detected amber siteid is {}, for nmi {}", sites.siteid, sites.nmi);
+            }
+
+            CurrentPrices currentPrices = webTargets.getCurrentPrices(siteID, apiKey);
+            final String electricityUnit = " AUD/kWh";
+
+            updateStatus(ThingStatus.ONLINE);
+            Unit<?> unit = CurrencyUnits.getInstance().getUnit("AUD");
+            if (unit == null) {
+                logger.trace("Currency AUD is unknown, falling back to DecimalType");
+                updateState(AmberElectricBindingConstants.CHANNEL_ELECTRICITY_PRICE,
+                        new DecimalType(currentPrices.elecPerKwh / 100));
+                updateState(AmberElectricBindingConstants.CHANNEL_CONTROLLED_LOAD_PRICE,
+                        new DecimalType(currentPrices.clPerKwh / 100));
+                updateState(AmberElectricBindingConstants.CHANNEL_FEED_IN_PRICE,
+                        new DecimalType(currentPrices.feedInPerKwh / 100));
+            } else {
+                updateState(AmberElectricBindingConstants.CHANNEL_ELECTRICITY_PRICE,
+                        new QuantityType<>(currentPrices.elecPerKwh / 100 + " " + electricityUnit));
+                updateState(AmberElectricBindingConstants.CHANNEL_CONTROLLED_LOAD_PRICE,
+                        new QuantityType<>(currentPrices.clPerKwh / 100 + " " + electricityUnit));
+                updateState(AmberElectricBindingConstants.CHANNEL_FEED_IN_PRICE,
+                        new QuantityType<>(currentPrices.feedInPerKwh / 100 + " " + electricityUnit));
+            }
+            updateState(AmberElectricBindingConstants.CHANNEL_CONTROLLED_LOAD_STATUS,
+                    new StringType(currentPrices.clStatus));
+            updateState(AmberElectricBindingConstants.CHANNEL_ELECTRICITY_STATUS,
+                    new StringType(currentPrices.elecStatus));
+            updateState(AmberElectricBindingConstants.CHANNEL_FEED_IN_STATUS,
+                    new StringType(currentPrices.feedInStatus));
+            updateState(AmberElectricBindingConstants.CHANNEL_NEM_TIME, new StringType(currentPrices.nemTime));
+            updateState(AmberElectricBindingConstants.CHANNEL_RENEWABLES, new DecimalType(currentPrices.renewables));
+            updateState(AmberElectricBindingConstants.CHANNEL_SPIKE,
+                    OnOffType.from(!"none".equals(currentPrices.spikeStatus)));
+        } catch (AmberElectricCommunicationException e) {
+            logger.debug("Unexpected error connecting to Amber Electric API", e);
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+        }
+    }
+}
diff --git a/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricHandlerFactory.java b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricHandlerFactory.java
new file mode 100644 (file)
index 0000000..d46f1f1
--- /dev/null
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2010-2024 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.amberelectric.internal;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * The {@link AmberElectricHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Paul Smedley - Initial contribution
+ */
+@Component(service = ThingHandlerFactory.class, configurationPid = "binding.amberelectric")
+@NonNullByDefault
+public class AmberElectricHandlerFactory extends BaseThingHandlerFactory {
+
+    @Override
+    public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+        return AmberElectricBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+    }
+
+    @Override
+    protected @Nullable ThingHandler createHandler(Thing thing) {
+        ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+        if (thingTypeUID.equals(AmberElectricBindingConstants.AMBERELECTRIC_THING)) {
+            return new AmberElectricHandler(thing);
+        }
+
+        return null;
+    }
+}
diff --git a/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricWebTargets.java b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricWebTargets.java
new file mode 100644 (file)
index 0000000..d51d2e6
--- /dev/null
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2010-2024 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.amberelectric.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.amberelectric.internal.api.CurrentPrices;
+import org.openhab.binding.amberelectric.internal.api.Sites;
+import org.openhab.core.io.net.http.HttpUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Handles performing the actual HTTP requests for communicating with the AmberAPI.
+ *
+ * @author Paul Smedley - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class AmberElectricWebTargets {
+    private static final int TIMEOUT_MS = 30000;
+    private static final String BASE_URI = "https://api.amber.com.au/v1/";
+    private final Logger logger = LoggerFactory.getLogger(AmberElectricWebTargets.class);
+
+    public AmberElectricWebTargets() {
+    }
+
+    public Sites getSites(String apiKey, String nmi) throws AmberElectricCommunicationException {
+        String getSitesUri = BASE_URI + "sites";
+        String response = invoke("GET", getSitesUri, apiKey);
+        logger.trace("Received response: \"{}\"", response);
+        return Sites.parse(response, nmi);
+    }
+
+    public CurrentPrices getCurrentPrices(String siteid, String apiKey) throws AmberElectricCommunicationException {
+        String getCurrentPricesUri = BASE_URI + "sites/" + siteid + "/prices/current";
+        String response = invoke("GET", getCurrentPricesUri, apiKey);
+        logger.trace("Received response: \"{}\"", response);
+        return CurrentPrices.parse(response);
+    }
+
+    protected Properties getHttpHeaders(String accessToken) {
+        Properties httpHeaders = new Properties();
+        httpHeaders.put("Authorization", "Bearer " + accessToken);
+        httpHeaders.put("Content-Type", "application/json");
+        return httpHeaders;
+    }
+
+    private String invoke(String httpMethod, String uri, String accessToken)
+            throws AmberElectricCommunicationException {
+        return invoke(httpMethod, uri, accessToken, null, null);
+    }
+
+    private String invoke(String httpMethod, String uri, String apiKey, @Nullable InputStream content,
+            @Nullable String contentType) throws AmberElectricCommunicationException {
+        logger.debug("Calling url: {}", uri);
+        @Nullable
+        String response;
+        try {
+            response = HttpUtil.executeUrl(httpMethod, uri, getHttpHeaders(apiKey), content, contentType, TIMEOUT_MS);
+        } catch (IOException ex) {
+            logger.debug("{}", ex.getLocalizedMessage(), ex);
+            // Response will also be set to null if parsing in executeUrl fails so we use null here to make the
+            // error check below consistent.
+            response = null;
+        }
+
+        if (response == null) {
+            throw new AmberElectricCommunicationException(
+                    String.format("AmberElectric returned no response while invoking %s", uri));
+        }
+        return response;
+    }
+}
diff --git a/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/api/CurrentPrices.java b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/api/CurrentPrices.java
new file mode 100644 (file)
index 0000000..894bb4b
--- /dev/null
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2010-2024 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.amberelectric.internal.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+/**
+ * Container class for Current Pricing, related to amberelectric
+ *
+ * @author Paul Smedley <paul@smedley.id.au> - Initial contribution
+ *
+ */
+@NonNullByDefault
+public class CurrentPrices {
+    public double elecPerKwh;
+    public double clPerKwh;
+    public double feedInPerKwh;
+    public String elecStatus = "";
+    public String clStatus = "";
+    public String feedInStatus = "";
+    public double renewables;
+    public String spikeStatus = "";
+    public String nemTime = "";
+
+    private CurrentPrices() {
+    }
+
+    public static CurrentPrices parse(String response) {
+        /* parse json string */
+        JsonArray jsonArray = JsonParser.parseString(response).getAsJsonArray();
+        JsonObject jsonObject = jsonArray.get(0).getAsJsonObject();
+        CurrentPrices currentprices = new CurrentPrices();
+        currentprices.nemTime = jsonObject.get("nemTime").getAsString();
+        currentprices.renewables = jsonObject.get("renewables").getAsDouble();
+        currentprices.spikeStatus = jsonObject.get("spikeStatus").getAsString();
+        for (int i = 0; i < jsonArray.size(); i++) {
+            jsonObject = jsonArray.get(i).getAsJsonObject();
+            if ("general".equals(jsonObject.get("channelType").getAsString())) {
+                currentprices.elecPerKwh = jsonObject.get("perKwh").getAsDouble();
+                currentprices.elecStatus = jsonObject.get("descriptor").getAsString();
+            }
+            if ("feedIn".equals(jsonObject.get("channelType").getAsString())) {
+                // Multiple value from API by -1 to make the value match the app
+                currentprices.feedInPerKwh = -1 * jsonObject.get("perKwh").getAsDouble();
+                currentprices.feedInStatus = jsonObject.get("descriptor").getAsString();
+            }
+            if ("controlledLoad".equals(jsonObject.get("channelType").getAsString())) {
+                currentprices.clPerKwh = jsonObject.get("perKwh").getAsDouble();
+                currentprices.clStatus = jsonObject.get("descriptor").getAsString();
+            }
+        }
+        return currentprices;
+    }
+}
diff --git a/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/api/Sites.java b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/api/Sites.java
new file mode 100644 (file)
index 0000000..d387178
--- /dev/null
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2010-2024 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.amberelectric.internal.api;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+/**
+ * Class for holding the set of parameters used to read the controller variables.
+ *
+ * @author Paul Smedley - Initial Contribution
+ *
+ */
+@NonNullByDefault
+public class Sites {
+    public String siteid = "";
+    public String nmi = "";
+
+    private Sites() {
+    }
+
+    public static Sites parse(String response, String nem) {
+        /* parse json string */
+        JsonArray jsonArray = JsonParser.parseString(response).getAsJsonArray();
+        Sites sites = new Sites();
+        for (int i = 0; i < jsonArray.size(); i++) {
+            JsonObject jsonObject = jsonArray.get(i).getAsJsonObject();
+            if (nem.equals(jsonObject.get("nmi").getAsString())) {
+                sites.siteid = jsonObject.get("id").getAsString();
+                sites.nmi = jsonObject.get("nmi").getAsString();
+            }
+        }
+        if ((nem.isEmpty()) || (sites.siteid.isEmpty())) { // nem not specified, or not found so we take the first
+                                                           // siteid
+                                                           // found
+            JsonObject jsonObject = jsonArray.get(0).getAsJsonObject();
+            sites.siteid = jsonObject.get("id").getAsString();
+            sites.nmi = jsonObject.get("nmi").getAsString();
+        }
+        return sites;
+    }
+}
diff --git a/bundles/org.openhab.binding.amberelectric/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.amberelectric/src/main/resources/OH-INF/addon/addon.xml
new file mode 100644 (file)
index 0000000..a5af8eb
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<addon:addon id="amberelectric" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
+
+       <type>binding</type>
+       <name>Amber Electric Binding</name>
+       <description>This is the binding for Amber Electric.</description>
+       <connection>cloud</connection>
+
+</addon:addon>
diff --git a/bundles/org.openhab.binding.amberelectric/src/main/resources/OH-INF/i18n/amberelectric.properties b/bundles/org.openhab.binding.amberelectric/src/main/resources/OH-INF/i18n/amberelectric.properties
new file mode 100644 (file)
index 0000000..fc39b85
--- /dev/null
@@ -0,0 +1,43 @@
+# add-on
+
+addon.amberelectric.name = Amber Electric Binding
+addon.amberelectric.description = This is the binding for Amber Electric.
+
+# thing types
+
+thing-type.amberelectric.service.label = Amber Electric
+thing-type.amberelectric.service.description = Amber Electric - wholesale access to power prices
+thing-type.amberelectric.service.channel.controlled-load-price.label = Current Controlled Load Price
+thing-type.amberelectric.service.channel.controlled-load-price.description = Current price to import power for Controlled Load
+thing-type.amberelectric.service.channel.controlled-load-status.label = Current Controlled Load Status
+thing-type.amberelectric.service.channel.controlled-load-status.description = Current price status of Controlled Load
+thing-type.amberelectric.service.channel.feed-in-price.label = Current Feed-In Price
+thing-type.amberelectric.service.channel.feed-in-price.description = Current price to export power to the grid
+thing-type.amberelectric.service.channel.feed-in-status.label = Current Feed-In Status
+thing-type.amberelectric.service.channel.feed-in-status.description = Current price status of Feed-In
+
+# thing types config
+
+thing-type.config.amberelectric.service.apiKey.label = API Key
+thing-type.config.amberelectric.service.apiKey.description = API key from the Amber website
+thing-type.config.amberelectric.service.nmi.label = NMI
+thing-type.config.amberelectric.service.nmi.description = NMI for your address (Optional)
+thing-type.config.amberelectric.service.refresh.label = Refresh Interval
+thing-type.config.amberelectric.service.refresh.description = Specifies the refresh interval in seconds
+
+# channel types
+
+channel-type.amberelectric.electricity-price.label = Current Electricity Price
+channel-type.amberelectric.electricity-price.description = Current price to import power from the grid
+channel-type.amberelectric.electricity-status.label = Current Electricity Status
+channel-type.amberelectric.electricity-status.description = Current price status of grid import
+channel-type.amberelectric.nem-time.label = NEM Time
+channel-type.amberelectric.nem-time.description = NEM time of last pricing update
+channel-type.amberelectric.renewables.label = Current Renewables
+channel-type.amberelectric.renewables.description = Current level of renewables in the grid
+channel-type.amberelectric.spike.label = Energy Price Spike
+channel-type.amberelectric.spike.description = Report if the grid has a current price spike
+
+# thing status descriptions
+
+offline.conf-error.no-api-key = API key must be set
diff --git a/bundles/org.openhab.binding.amberelectric/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.amberelectric/src/main/resources/OH-INF/thing/thing-types.xml
new file mode 100644 (file)
index 0000000..af7dea0
--- /dev/null
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="amberelectric"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+       xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+       <thing-type id="service">
+               <label>Amber Electric</label>
+               <description>Amber Electric - wholesale access to power prices</description>
+
+               <channels>
+                       <channel id="electricity-price" typeId="electricity-price"/>
+                       <channel id="controlled-load-price" typeId="electricity-price">
+                               <label>Current Controlled Load Price</label>
+                               <description>Current price to import power for Controlled Load</description>
+                       </channel>
+                       <channel id="feed-in-price" typeId="electricity-price">
+                               <label>Current Feed-In Price</label>
+                               <description>Current price to export power to the grid</description>
+                       </channel>
+                       <channel id="electricity-status" typeId="electricity-status"/>
+                       <channel id="controlled-load-status" typeId="electricity-status">
+                               <label>Current Controlled Load Status</label>
+                               <description>Current price status of Controlled Load</description>
+                       </channel>
+                       <channel id="feed-in-status" typeId="electricity-status">
+                               <label>Current Feed-In Status</label>
+                               <description>Current price status of Feed-In</description>
+                       </channel>
+                       <channel id="nem-time" typeId="nemtime"/>
+                       <channel id="spike" typeId="spike"/>
+                       <channel id="renewables" typeId="renewables"/>
+               </channels>
+
+               <config-description>
+                       <parameter name="apiKey" type="text" required="true">
+                               <label>API Key</label>
+                               <description>API key from the Amber website</description>
+                       </parameter>
+                       <parameter name="nmi" type="text">
+                               <label>NMI</label>
+                               <description>NMI for your address (Optional)</description>
+                       </parameter>
+                       <parameter name="refresh" type="integer" min="60" unit="s">
+                               <label>Refresh Interval</label>
+                               <description>Specifies the refresh interval in seconds</description>
+                               <default>60</default>
+                       </parameter>
+               </config-description>
+
+       </thing-type>
+
+       <channel-type id="electricity-price">
+               <item-type>Number:EnergyPrice</item-type>
+               <label>Current Electricity Price</label>
+               <description>Current price to import power from the grid</description>
+               <category>Price</category>
+               <state readOnly="true" pattern="%.3f %unit%"/>
+       </channel-type>
+       <channel-type id="electricity-status">
+               <item-type>String</item-type>
+               <label>Current Electricity Status</label>
+               <description>Current price status of grid import</description>
+               <state readOnly="true" pattern="%s"/>
+       </channel-type>
+       <channel-type id="nem-time">
+               <item-type>String</item-type>
+               <label>NEM Time</label>
+               <description>NEM time of last pricing update</description>
+               <state readOnly="true" pattern="%s"/>
+       </channel-type>
+       <channel-type id="renewables">
+               <item-type unitHint="%">Number:Dimensionless</item-type>
+               <label>Current Renewables</label>
+               <description>Current level of renewables in the grid</description>
+               <state readOnly="true" pattern="%d %unit%"/>
+       </channel-type>
+       <channel-type id="spike">
+               <item-type>Switch</item-type>
+               <label>Energy Price Spike</label>
+               <description>Report if the grid has a current price spike</description>
+               <state readOnly="true"/>
+       </channel-type>
+</thing:thing-descriptions>
index 67d903323cecebe53c67ae91ac6fb18b7870b955..c0c84ea98b8f7a2a394edd327a6feaba99fb4234 100644 (file)
@@ -55,6 +55,7 @@
     <module>org.openhab.binding.allplay</module>
     <module>org.openhab.binding.amazondashbutton</module>
     <module>org.openhab.binding.amazonechocontrol</module>
+    <module>org.openhab.binding.amberelectric</module>
     <module>org.openhab.binding.ambientweather</module>
     <module>org.openhab.binding.amplipi</module>
     <module>org.openhab.binding.androiddebugbridge</module>