Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions bundles/org.openhab.binding.tibber/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

The Tibber Binding retrieves `prices` from the [Tibber API](https://developer.tibber.com).
If you have Tibber Pulse hardware, you can also use the [live group](#live-group) and [statistics group](#statistics-group).
Historical energy data (consumption, cost, and production) is available in the [history group](#history-group) for all users.

## Supported Things

Expand Down Expand Up @@ -124,6 +125,37 @@ All values are read-only.
| daily-production | Number:Energy | Net energy produced since midnight in kilowatt-hours |
| last-hour-production | Number:Energy | Net energy produced since last hour shift in kilowatt-hours |

### `history` group

Historical energy consumption, cost, and production delivered as time series.
All values are read-only.

The Tibber API only returns **completed** periods — for example, an annual query run in May 2026 returns data up to end of 2025.
The binding automatically fills the gap for the current incomplete period by aggregating data bottom-up:
daily entries are summed into the current week, weekly entries into the current month, and monthly entries into the current year.
These synthetic entries are transient and will be replaced by real API data once the period closes.

| Channel ID | Type | Description | Time Series |
|---------------------|-----------------|--------------------------------------|-------------|
| yearly-consumption | Number:Energy | Yearly energy consumption | yes |
| yearly-cost | Number:Currency | Yearly energy costs | yes |
| yearly-production | Number:Energy | Yearly energy production | yes |
| monthly-consumption | Number:Energy | Monthly energy consumption | yes |
| monthly-cost | Number:Currency | Monthly energy costs | yes |
| monthly-production | Number:Energy | Monthly energy production | yes |
| weekly-consumption | Number:Energy | Weekly energy consumption | yes |
| weekly-cost | Number:Currency | Weekly energy costs | yes |
| weekly-production | Number:Energy | Weekly energy production | yes |
| daily-consumption | Number:Energy | Daily energy consumption | yes |
| daily-cost | Number:Currency | Daily energy costs | yes |
| daily-production | Number:Energy | Daily energy production | yes |

History data is fetched automatically when a channel is linked to an item, and refreshed daily at 01:00.
A full re-fetch for any time window can be triggered manually via the [`fetchHistory`](#fetchhistory) Thing Action.

Please note that time series are not supported by the default [rrd4j](https://www.openhab.org/addons/persistence/rrd4j/) persistence.
The items connected to the above channels need to be stored in, e.g., [InfluxDB](https://www.openhab.org/addons/persistence/influxdb/) or [InMemory](https://www.openhab.org/addons/persistence/inmemory/).

## Thing Actions

Thing actions can be used to perform calculations on the currently available price information cached by the binding.
Expand Down Expand Up @@ -439,6 +471,32 @@ JSON Object `scheduleEntry`
}
```

### `fetchHistory`

Triggers a full history fetch for a specific time window and stores the result persistently.
Use this action to manually refresh history data, for example after the binding has been offline for an extended period.

#### Parameters

| Name | Type | Description | Required |
|--------|--------|-----------------------------------------------------|----------|
| window | String | Time window to fetch: `ANNUAL`, `MONTHLY`, `WEEKLY`, `DAILY` | yes |

#### Example

```java
rule "Tibber Fetch Full History"
when
System started // use your trigger
then
var actions = getActions("tibber","tibber:tibberapi:xyz")
actions.fetchHistory("ANNUAL")
actions.fetchHistory("MONTHLY")
actions.fetchHistory("WEEKLY")
actions.fetchHistory("DAILY")
end
```

## Full Example

Full example with `demo.things` and `demo.items`
Expand Down Expand Up @@ -478,6 +536,19 @@ Number:Energy Tibber_API_Last_Hour_Consumption "Last Hour Consu
Number:Energy Tibber_API_Total_Production "Total Production" {channel="tibber:tibberapi:xyz:statistics#total-production"}
Number:Energy Tibber_API_Daily_Production "Daily Production" {channel="tibber:tibberapi:xyz:statistics#daily-production"}
Number:Energy Tibber_API_Last_Hour_Production "Last Hour Production" {channel="tibber:tibberapi:xyz:statistics#last-hour-production"}

Number:Energy Tibber_API_Yearly_Consumption "Yearly Consumption" {channel="tibber:tibberapi:xyz:history#yearly-consumption"}
Number:Currency Tibber_API_Yearly_Cost "Yearly Cost" {channel="tibber:tibberapi:xyz:history#yearly-cost"}
Number:Energy Tibber_API_Yearly_Production "Yearly Production" {channel="tibber:tibberapi:xyz:history#yearly-production"}
Number:Energy Tibber_API_Monthly_Consumption "Monthly Consumption" {channel="tibber:tibberapi:xyz:history#monthly-consumption"}
Number:Currency Tibber_API_Monthly_Cost "Monthly Cost" {channel="tibber:tibberapi:xyz:history#monthly-cost"}
Number:Energy Tibber_API_Monthly_Production "Monthly Production" {channel="tibber:tibberapi:xyz:history#monthly-production"}
Number:Energy Tibber_API_Weekly_Consumption "Weekly Consumption" {channel="tibber:tibberapi:xyz:history#weekly-consumption"}
Number:Currency Tibber_API_Weekly_Cost "Weekly Cost" {channel="tibber:tibberapi:xyz:history#weekly-cost"}
Number:Energy Tibber_API_Weekly_Production "Weekly Production" {channel="tibber:tibberapi:xyz:history#weekly-production"}
Number:Energy Tibber_API_History_Daily_Consumption "History Daily Consumption" {channel="tibber:tibberapi:xyz:history#daily-consumption"}
Number:Currency Tibber_API_History_Daily_Cost "History Daily Cost" {channel="tibber:tibberapi:xyz:history#daily-cost"}
Number:Energy Tibber_API_History_Daily_Production "History Daily Production" {channel="tibber:tibberapi:xyz:history#daily-production"}
```

### Rule listen to day-ahead price update
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* @author Stian Kjoglum - Initial contribution
* @author Bernd Weymann - Enhance used constants
* @author Bernd Weymann - Add Map collection for price / cost channels
* @author Bernd Weymann - Add history channel group
*/
@NonNullByDefault
public class TibberBindingConstants {
Expand All @@ -44,6 +45,7 @@ public class TibberBindingConstants {
public static final String CHANNEL_GROUP_PRICE = "price";
public static final String CHANNEL_GROUP_LIVE = "live";
public static final String CHANNEL_GROUP_STATISTICS = "statistics";
public static final String CHANNEL_GROUP_HISTORY = "history";

// price channels
public static final String CHANNEL_TOTAL_PRICE = "total";
Expand Down Expand Up @@ -79,6 +81,20 @@ public class TibberBindingConstants {
public static final String CHANNEL_DAILY_PRODUCTION = "daily-production";
public static final String CHANNEL_LAST_HOUR_PRODUCTION = "last-hour-production";

// history channels
public static final String CHANNEL_YEARLY_CONSUMPTION = "yearly-consumption";
public static final String CHANNEL_YEARLY_COST = "yearly-cost";
public static final String CHANNEL_YEARLY_PRODUCTION = "yearly-production";
public static final String CHANNEL_MONTHLY_CONSUMPTION = "monthly-consumption";
public static final String CHANNEL_MONTHLY_COST = "monthly-cost";
public static final String CHANNEL_MONTHLY_PRODUCTION = "monthly-production";
public static final String CHANNEL_WEEKLY_CONSUMPTION = "weekly-consumption";
public static final String CHANNEL_WEEKLY_COST = "weekly-cost";
public static final String CHANNEL_WEEKLY_PRODUCTION = "weekly-production";
public static final String CHANNEL_HISTORY_DAILY_CONSUMPTION = "daily-consumption";
public static final String CHANNEL_HISTORY_DAILY_COST = "daily-cost";
public static final String CHANNEL_HISTORY_DAILY_PRODUCTION = "daily-production";

// List of all events
public static final String EVENT_DAY_AHEAD_AVAILABLE = "DAY_AHEAD_AVAILABLE";

Expand All @@ -94,6 +110,8 @@ public class TibberBindingConstants {
public static final String PRICE_QUERY_RESOURCE_PATH = "/graphql/prices.graphql";
public static final String REALTIME_QUERY_RESOURCE_PATH = "/graphql/realtime.graphql";
public static final String WEBSOCKET_SUBSCRIPTION_RESOURCE_PATH = "/graphql/websocket.graphql";
public static final String CONSUMPTION_QUERY_RESOURCE_PATH = "/graphql/consumption.graphql";
public static final String PRODUCTION_QUERY_RESOURCE_PATH = "/graphql/production.graphql";

public static final String SCHEDULE_CONTAINER = "{\"size\":%s, \"cost\":%s, \"schedule\":%s}";
public static final String QUERY_CONTAINER = "{\"query\":\"%s\"}";
Expand All @@ -102,6 +120,9 @@ public class TibberBindingConstants {
public static final String DISCONNECT_MESSAGE = "{\"type\":\"connection_terminate\",\"payload\":null}";
public static final String SUBSCRIPTION_MESSAGE = "{\"id\":\"1\",\"type\":\"subscribe\",\"payload\":{\"variables\":{},\"extensions\":{},\"operationName\":null,\"query\":\"subscription { liveMeasurement(homeId:\\\"%s\\\") { timestamp power lastMeterConsumption lastMeterProduction accumulatedConsumption accumulatedConsumptionLastHour accumulatedCost currency minPower averagePower maxPower voltagePhase1 voltagePhase2 voltagePhase3 currentL1 currentL2 currentL3 powerProduction accumulatedProduction accumulatedProductionLastHour minPowerProduction maxPowerProduction }}\"}}";

public static final String[] HISTORY_CONSUMPTION_JSON_PATH = { "data", "viewer", "home", "consumption" };
public static final String[] HISTORY_PRODUCTION_JSON_PATH = { "data", "viewer", "home", "production" };

public static final String[] INITIAL_QUERY_JSON_PATH = new String[] { "data", "viewer", "home" };
public static final String[] PRICE_INFO_JSON_PATH = new String[] { "data", "viewer", "home", "currentSubscription",
"priceInfo" };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.scheduler.CronScheduler;
import org.openhab.core.storage.StorageService;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
Expand All @@ -35,20 +36,24 @@
*
* @author Stian Kjoglum - Initial contribution
* @author Bernd Weymann - Use HttpClientFactory, CronScheduler and TimeZoneProvider
* @author Bernd Weymann - Add StorageService for history persistence
* @author Bernd Weymann - Add history channel group
*/
@NonNullByDefault
@Component(configurationPid = "binding.tibber", service = ThingHandlerFactory.class)
public class TibberHandlerFactory extends BaseThingHandlerFactory {
private final HttpClientFactory httpFactory;
private final CronScheduler cron;
private final TimeZoneProvider timeZoneProvider;
private final StorageService storageService;

@Activate
public TibberHandlerFactory(final @Reference HttpClientFactory httpFactory, final @Reference CronScheduler cron,
final @Reference TimeZoneProvider timeZoneProvider) {
final @Reference TimeZoneProvider timeZoneProvider, final @Reference StorageService storageService) {
this.httpFactory = httpFactory;
this.cron = cron;
this.timeZoneProvider = timeZoneProvider;
this.storageService = storageService;
}

@Override
Expand All @@ -60,7 +65,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(TIBBER_THING_TYPE)) {
return new TibberHandler(thing, httpFactory.getCommonHttpClient(), cron, bundleContext, timeZoneProvider);
return new TibberHandler(thing, httpFactory.getCommonHttpClient(), cron, bundleContext, timeZoneProvider,
storageService);
} else {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.openhab.binding.tibber.internal.exception.CalculationParameterException;
import org.openhab.binding.tibber.internal.exception.PriceCalculationException;
import org.openhab.binding.tibber.internal.handler.TibberHandler;
import org.openhab.binding.tibber.internal.history.TibberHistory;
import org.openhab.core.automation.annotation.ActionInput;
import org.openhab.core.automation.annotation.ActionOutput;
import org.openhab.core.automation.annotation.RuleAction;
Expand Down Expand Up @@ -219,6 +220,26 @@ public class TibberActions implements ThingActions {
}
}

@RuleAction(label = "@text/actionFetchHistoryLabel", description = "@text/actionFetchHistoryDescription")
public void fetchHistory(
@ActionInput(name = "window", label = "@text/actionInputWindowLabel", type = "java.lang.String") String window) {
TibberHandler thingHandler = this.thingHandler;
if (thingHandler == null) {
logger.warn("No Thing attached to Actions! Maybe OFFLINE or Thing deactivated.");
return;
}
try {
TibberHistory.TimeWindow timeWindow = TibberHistory.TimeWindow.valueOf(window.toUpperCase());
thingHandler.fetchHistory(timeWindow);
} catch (IllegalArgumentException e) {
logger.warn("Unknown history window '{}'. Valid values: ANNUAL, MONTHLY, WEEKLY, DAILY", window);
}
}

public static void fetchHistory(ThingActions actions, String window) {
((TibberActions) actions).fetchHistory(window);
}

public static Instant priceInfoStart(ThingActions actions) {
return ((TibberActions) actions).priceInfoStart();
}
Expand Down
Loading
Loading