diff --git a/CODEOWNERS b/CODEOWNERS
index 0511f63f7dc..7cb21544f80 100755
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -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
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index f97b4b7010c..c1e15edf948 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -101,6 +101,11 @@
org.openhab.binding.amazonechocontrol
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.amberelectric
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.ambientweather
diff --git a/bundles/org.openhab.binding.amberelectric/NOTICE b/bundles/org.openhab.binding.amberelectric/NOTICE
new file mode 100644
index 00000000000..38d625e3492
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/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/openhab-addons
diff --git a/bundles/org.openhab.binding.amberelectric/README.md b/bundles/org.openhab.binding.amberelectric/README.md
new file mode 100644
index 00000000000..f856eddbd42
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/README.md
@@ -0,0 +1,69 @@
+# Amber Electric Binding
+
+A binding that supports the Australian energy retailer Amber's API () 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
+- '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
index 00000000000..cdb9cdf2556
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 4.2.0-SNAPSHOT
+
+
+ org.openhab.binding.amberelectric
+
+ openHAB Add-ons :: Bundles :: Amber Electric Binding
+
+
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
index 00000000000..233fe0394aa
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/src/main/feature/feature.xml
@@ -0,0 +1,9 @@
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.amberelectric/${project.version}
+
+
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
index 00000000000..004bdc7c567
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricBindingConstants.java
@@ -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 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
index 00000000000..d8a9c45dec5
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricCommunicationException.java
@@ -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
index 00000000000..eaf1157bfca
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricConfiguration.java
@@ -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
index 00000000000..aa91c4bab75
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricHandler.java
@@ -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
index 00000000000..d46f1f1bd6d
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricHandlerFactory.java
@@ -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
index 00000000000..d51d2e6145e
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/AmberElectricWebTargets.java
@@ -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
index 00000000000..894bb4bc1c9
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/api/CurrentPrices.java
@@ -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 - 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
index 00000000000..d387178b7c9
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/src/main/java/org/openhab/binding/amberelectric/internal/api/Sites.java
@@ -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
index 00000000000..a5af8ebd25c
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/src/main/resources/OH-INF/addon/addon.xml
@@ -0,0 +1,11 @@
+
+
+
+ binding
+ Amber Electric Binding
+ This is the binding for Amber Electric.
+ cloud
+
+
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
index 00000000000..fc39b8515fb
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/src/main/resources/OH-INF/i18n/amberelectric.properties
@@ -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
index 00000000000..af7dea0c79e
--- /dev/null
+++ b/bundles/org.openhab.binding.amberelectric/src/main/resources/OH-INF/thing/thing-types.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+ Amber Electric - wholesale access to power prices
+
+
+
+
+
+ Current price to import power for Controlled Load
+
+
+
+ Current price to export power to the grid
+
+
+
+
+ Current price status of Controlled Load
+
+
+
+ Current price status of Feed-In
+
+
+
+
+
+
+
+
+
+ API key from the Amber website
+
+
+
+ NMI for your address (Optional)
+
+
+
+ Specifies the refresh interval in seconds
+ 60
+
+
+
+
+
+
+ Number:EnergyPrice
+
+ Current price to import power from the grid
+ Price
+
+
+
+ String
+
+ Current price status of grid import
+
+
+
+ String
+
+ NEM time of last pricing update
+
+
+
+ Number:Dimensionless
+
+ Current level of renewables in the grid
+
+
+
+ Switch
+
+ Report if the grid has a current price spike
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index cc9981caea2..2fb7552f2eb 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -55,6 +55,7 @@
org.openhab.binding.allplay
org.openhab.binding.amazondashbutton
org.openhab.binding.amazonechocontrol
+ org.openhab.binding.amberelectric
org.openhab.binding.ambientweather
org.openhab.binding.amplipi
org.openhab.binding.androiddebugbridge