commit c19736791696316c65b54ba26527207e52b57a31 Author: Stephen Newey Date: Sat Nov 10 15:28:41 2018 +0000 Inital commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..6e1a2ee --- /dev/null +++ b/README.rst @@ -0,0 +1,69 @@ +Octograph +--------- + +Python tool for downloading energy consumption data from the +`Octopus Energy API`_ and loading it into `InfluxDB`_. + +In the process, additional metrics will be generated and stored for unit rates +and costs as configured by the user. Suitable for two-rate electricity tariffs +like `Octopus Energy Go`_. Single rate gas readings are also retrieved and +stored. + +The secondary unit rate is specified with start and end times, and a timezone +which is useful for the Go tariff where the discount rate changes with +daylight savings time. + +Included is an example `Grafana`_ dashboard to visualise the captured data. + +An example Docker Compose file is included for easily running InfluxDB and +Grafana. + +.. image:: grafana-dashboard.png + :width: 800 + +Installation +============ + +Tested on macOS with Docker for Mac and Python 3.6. A Python virtualenv is +recommended. + +Install the Python requirements with ``pip`` + +.. code:: bash + + pip install -r app/requirements.txt + + +Usage +===== + +Create a configuration file ``octograph.ini`` customised with your Octopus +API key, meter details and energy rate information. This file should be in the +working directory where you run the ``octopus_to_influxdb.py`` command, or +can be passed as an argument. + +.. code:: bash + + python app/octopus_to_influxdb.py --help + +By default, energy data for the previous day will be collected. Optional from +and to ranges may be specified to retrieve larger datasets. It is anticipated +that the script will be run daily by a cron job. + +.. code:: bash + + docker-compose up -d # start InfluxDB and Grafana in Docker + python app/octopus_to_infuxdb.py --from-date=2018-10-20 + open http://localhost:3000 + +The default login credentials for Grafana are admin/admin, and you will be +prompted to set a new password on first login. You should then proceed to add +InfluxDB as a datasource with URL ``http://influxdb:8086`` and database +``energy`` if using the Docker version provided. The dashboard provided can +then be imported to review the data. + + +.. _Octopus Energy API: https://developer.octopus.energy/docs/api/ +.. _Octopus Energy Go: https://octopus.energy/go/ +.. _InfluxDB: https://www.influxdata.com/time-series-platform/influxdb/ +.. _Grafana: https://grafana.com diff --git a/app/.dockerignore b/app/.dockerignore new file mode 100644 index 0000000..c91a7b6 --- /dev/null +++ b/app/.dockerignore @@ -0,0 +1 @@ +octograph.ini diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..c91a7b6 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +octograph.ini diff --git a/app/Dockerfile b/app/Dockerfile new file mode 100644 index 0000000..0e9bf11 --- /dev/null +++ b/app/Dockerfile @@ -0,0 +1,12 @@ +FROM python:3.6 + +LABEL maintainer="Stephen Newey " + +WORKDIR /usr/src/app + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY octopus_to_influxdb.py ./ + +ENTRYPOINT [ "python", "./octopus_to_influxdb.py" ] diff --git a/app/example-octograph.ini b/app/example-octograph.ini new file mode 100644 index 0000000..1ed6079 --- /dev/null +++ b/app/example-octograph.ini @@ -0,0 +1,25 @@ +[influxdb] +host = localhost +port = 8086 +user = user +password = password +database = energy + +[octopus] +api_key = sk_live_1234 + +[electricity] +mpan = 12345 +serial_number = 12A3456 +standing_charge = 25.00 +unit_rate_high = 14.12 +unit_rate_low = 5.00 +unit_rate_low_start = 00:30 +unit_rate_low_end = 04:30 +unit_rate_low_zone = Europe/London + +[gas] +mpan = 12345 +serial_number = 12A3456 +standing_charge = 16.80 +unit_rate = 3.03 diff --git a/app/octopus_to_influxdb.py b/app/octopus_to_influxdb.py new file mode 100644 index 0000000..5457963 --- /dev/null +++ b/app/octopus_to_influxdb.py @@ -0,0 +1,187 @@ +#!/usr/bin/env python + +from configparser import ConfigParser +from urllib import parse + +import click +import maya +import requests +from influxdb import InfluxDBClient + + +def retrieve_paginated_consumption( + api_key, url, from_date, to_date, page=None +): + args = { + 'period_from': from_date, + 'period_to': to_date, + } + if page: + args['page'] = page + response = requests.get(url, params=args, auth=(api_key, '')) + response.raise_for_status() + data = response.json() + results = data.get('results', []) + if data['next']: + url_query = parse.urlparse(data['next']).query + next_page = parse.parse_qs(url_query)['page'][0] + results += retrieve_paginated_consumption( + api_key, url, from_date, to_date, next_page + ) + return results + + +def store_series(connection, series, metrics, rate_data): + + def active_rate_field(measurement): + if series == 'gas': + return 'unit_rate' + elif not rate_data['unit_rate_low_zone']: # no low rate + return 'unit_rate_high' + + low_start_str = rate_data['unit_rate_low_start'] + low_end_str = rate_data['unit_rate_low_end'] + low_zone = rate_data['unit_rate_low_zone'] + + measurement_at = maya.parse(measurement['interval_start']) + + low_start = maya.when( + measurement_at.datetime().strftime(f'%Y-%m-%dT{low_start_str}'), + timezone=low_zone + ) + low_end = maya.when( + measurement_at.datetime().strftime(f'%Y-%m-%dT{low_end_str}'), + timezone=low_zone + ) + low_period = maya.MayaInterval(low_start, low_end) + + return \ + 'unit_rate_low' if measurement_at in low_period \ + else 'unit_rate_high' + + def fields_for_measurement(measurement): + consumption = measurement['consumption'] + rate = active_rate_field(measurement) + rate_cost = rate_data[rate] + cost = consumption * rate_cost + standing_charge = rate_data['standing_charge'] / 48 # 30 minute reads + return { + 'consumption': consumption, + 'cost': cost, + 'total_cost': cost + standing_charge, + } + + def tags_for_measurement(measurement): + period = maya.parse(measurement['interval_end']) + time = period.datetime().strftime('%H:%M') + return { + 'active_rate': active_rate_field(measurement), + 'time_of_day': time, + } + + measurements = [ + { + 'measurement': series, + 'tags': tags_for_measurement(measurement), + 'time': measurement['interval_end'], + 'fields': fields_for_measurement(measurement), + } + for measurement in metrics + ] + connection.write_points(measurements) + + +@click.command() +@click.option( + '--config-file', + default="octograph.ini", + type=click.Path(exists=True, dir_okay=True, readable=True), +) +@click.option('--from-date', default='yesterday midnight', type=click.STRING) +@click.option('--to-date', default='today midnight', type=click.STRING) +def cmd(config_file, from_date, to_date): + + config = ConfigParser() + config.read(config_file) + + influx = InfluxDBClient( + host=config.get('influxdb', 'host', fallback="localhost"), + port=config.getint('influxdb', 'port', fallback=8086), + username=config.get('influxdb', 'user', fallback=""), + password=config.get('influxdb', 'password', fallback=""), + database=config.get('influxdb', 'database', fallback="energy"), + ) + + api_key = config.get('octopus', 'api_key') + if not api_key: + raise click.ClickException('No Octopus API key set') + + e_mpan = config.get('electricity', 'mpan', fallback=None) + e_serial = config.get('electricity', 'serial_number', fallback=None) + if not e_mpan or not e_serial: + raise click.ClickException('No electricity meter identifiers') + e_url = 'https://api.octopus.energy/v1/electricity-meter-points/' \ + f'{e_mpan}/meters/{e_serial}/consumption/' + + g_mpan = config.get('gas', 'mpan', fallback=None) + g_serial = config.get('gas', 'serial_number', fallback=None) + if not g_mpan or not g_serial: + raise click.ClickException('No gas meter identifiers') + g_url = 'https://api.octopus.energy/v1/gas-meter-points/' \ + f'{g_mpan}/meters/{g_serial}/consumption/' + + rate_data = { + 'electricity': { + 'standing_charge': config.getfloat( + 'electricity', 'standing_charge', fallback=0.0 + ), + 'unit_rate_high': config.getfloat( + 'electricity', 'unit_rate_high', fallback=0.0 + ), + 'unit_rate_low': config.getfloat( + 'electricity', 'unit_rate_low', fallback=0.0 + ), + 'unit_rate_low_start': config.get( + 'electricity', 'unit_rate_low_start', fallback="00:00" + ), + 'unit_rate_low_end': config.get( + 'electricity', 'unit_rate_low_end', fallback="00:00" + ), + 'unit_rate_low_zone': config.get( + 'electricity', 'unit_rate_low_zone', fallback=None + ), + }, + 'gas': { + 'standing_charge': config.getfloat( + 'gas', 'standing_charge', fallback=0.0 + ), + 'unit_rate': config.getfloat('gas', 'unit_rate', fallback=0.0), + } + } + + from_iso = maya.when(from_date).iso8601() + to_iso = maya.when(to_date).iso8601() + + click.echo( + f'Retrieving electricity data for {from_iso} until {to_iso}...', + nl=False + ) + e_consumption = retrieve_paginated_consumption( + api_key, e_url, from_iso, to_iso + ) + click.echo(f' {len(e_consumption)} readings.') + store_series(influx, 'electricity', e_consumption, rate_data['electricity']) + + click.echo( + f'Retrieving gas data for {from_iso} until {to_iso}...', + nl=False + ) + g_consumption = retrieve_paginated_consumption( + api_key, g_url, from_iso, to_iso + ) + click.echo(f' {len(g_consumption)} readings.') + store_series(influx, 'gas', g_consumption, rate_data['gas']) + + +if __name__ == '__main__': + cmd() \ No newline at end of file diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..904a2da --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,4 @@ +requests >= 2.20.0 +influxdb == 5.2.0 +Click == 7.0 +maya == 0.5.0 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d62f6eb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3' +services: + influxdb: + image: influxdb + environment: + - INFLUXDB_DB=energy + ports: + - "8086:8086" + grafana: + image: grafana/grafana + ports: + - "3000:3000" + depends_on: + - influxdb diff --git a/energy_dashboard.json b/energy_dashboard.json new file mode 100644 index 0000000..1749f8f --- /dev/null +++ b/energy_dashboard.json @@ -0,0 +1,1102 @@ +{ + "__inputs": [ + { + "name": "DS_INFLUX", + "label": "influx", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "5.3.2" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "5.0.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "5.0.0" + }, + { + "type": "panel", + "id": "singlestat", + "name": "Singlestat", + "version": "5.0.0" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${DS_INFLUX}", + "format": "kwatth", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 0, + "y": 0 + }, + "id": 6, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "electricity", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "consumption" + ], + "type": "field" + }, + { + "params": [], + "type": "sum" + } + ] + ], + "tags": [] + } + ], + "thresholds": "", + "title": "Electricity used", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "total" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${DS_INFLUX}", + "decimals": 2, + "format": "currencyGBP", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 6, + "y": 0 + }, + "id": 10, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "electricity", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT sum(\"cost\")/100 FROM \"electricity\" WHERE $timeFilter GROUP BY time($__interval) fill(none)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "cost" + ], + "type": "field" + }, + { + "params": [], + "type": "sum" + } + ] + ], + "tags": [] + } + ], + "thresholds": "", + "title": "Electricity cost (units only)", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "total" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${DS_INFLUX}", + "decimals": 2, + "format": "currencyGBP", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 12, + "y": 0 + }, + "id": 12, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "electricity", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT sum(\"total_cost\")/100 FROM \"electricity\" WHERE $timeFilter GROUP BY time($__interval) fill(none)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "cost" + ], + "type": "field" + }, + { + "params": [], + "type": "sum" + } + ] + ], + "tags": [] + } + ], + "thresholds": "", + "title": "Electricity cost (total)", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "total" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_INFLUX}", + "fill": 1, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 2 + }, + "id": 2, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Cost", + "yaxis": 2, + "zindex": -1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "alias": "Usage", + "groupBy": [ + { + "params": [ + "30m" + ], + "type": "time" + }, + { + "params": [ + "linear" + ], + "type": "fill" + } + ], + "measurement": "electricity", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"consumption\") FROM \"electricity\" WHERE $timeFilter GROUP BY time(30m) fill(linear)", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "consumption" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + }, + { + "alias": "Cost", + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "linear" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "electricity", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"cost\")/100 FROM \"electricity\" WHERE $timeFilter GROUP BY time(30m) fill(linear)", + "rawQuery": true, + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "cost" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Electricity", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transparent": false, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "kwatth", + "label": "Usage", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "decimals": null, + "format": "currencyGBP", + "label": "Cost", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${DS_INFLUX}", + "format": "kwatth", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 0, + "y": 11 + }, + "id": 7, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "gas", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "consumption" + ], + "type": "field" + }, + { + "params": [], + "type": "sum" + } + ] + ], + "tags": [] + } + ], + "thresholds": "", + "title": "Gas used", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "total" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${DS_INFLUX}", + "decimals": 2, + "format": "currencyGBP", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 6, + "y": 11 + }, + "id": 11, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "electricity", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT sum(\"cost\")/100 FROM \"gas\" WHERE $timeFilter GROUP BY time($__interval) fill(none)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "cost" + ], + "type": "field" + }, + { + "params": [], + "type": "sum" + } + ] + ], + "tags": [] + } + ], + "thresholds": "", + "title": "Gas cost (units only)", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "total" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#299c46", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "${DS_INFLUX}", + "decimals": 2, + "format": "currencyGBP", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 6, + "x": 12, + "y": 11 + }, + "id": 13, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "electricity", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT sum(\"total_cost\")/100 FROM \"gas\" WHERE $timeFilter GROUP BY time($__interval) fill(none)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "cost" + ], + "type": "field" + }, + { + "params": [], + "type": "sum" + } + ] + ], + "tags": [] + } + ], + "thresholds": "", + "title": "Gas cost (total)", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "total" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_INFLUX}", + "fill": 1, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 13 + }, + "id": 8, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Cost", + "yaxis": 2, + "zindex": -1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "alias": "Usage", + "groupBy": [ + { + "params": [ + "30m" + ], + "type": "time" + }, + { + "params": [ + "linear" + ], + "type": "fill" + } + ], + "measurement": "gas", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"consumption\") FROM \"electricity\" WHERE $timeFilter GROUP BY time(30m) fill(linear)", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "consumption" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + }, + { + "alias": "Cost", + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "linear" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "electricity", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"cost\")/100 FROM \"gas\" WHERE $timeFilter GROUP BY time(30m) fill(linear)", + "rawQuery": true, + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "cost" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Gas", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transparent": false, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "kwatth", + "label": "Usage", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "decimals": null, + "format": "currencyGBP", + "label": "Cost", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 16, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-7d", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Energy usage", + "uid": "7Nqpoj-ik", + "version": 8 +} \ No newline at end of file diff --git a/grafana-dashboard.png b/grafana-dashboard.png new file mode 100644 index 0000000..6f3f477 Binary files /dev/null and b/grafana-dashboard.png differ