187 lines
6.1 KiB
Python
187 lines
6.1 KiB
Python
#!/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() |