eInk Display for Octopus's Agile Energy Tariff
I'm a little bit obsessed with building eInk displays. They're pretty cheap second hand. They're low energy, passive displays, with good-enough performance for occasional updates. Here's a new one which shows me what the current cost of my electricity is:
Background
After installing solar panels, a smart electricity meter, and a solar battery - the next obvious step was a smart energy tariff.
Octopus (join and we both get £50) have an "Agile" tariff. Unlike a normal tariff - with a set price for electricity - this tariff fluctuates every 30 minutes. Prices depend on wholesale costs which means they can go negative. That's right, you can get paid to soak up excess power.
Of course, they can also spike considerably. Unlike the failed Texas experiment, here the maximum price is capped at £1/kWh.
Every day at about 1600, the next day's prices are published on Octopus's website. And they're also made available via a simple REST API.
So, it's relatively simple to generate a line graph and display it on the eInk screen.
Code
(You can treat this code as MIT Licenced if that makes you happy.)
Calling the API for the half-houly prices is:
PHP$url = "https://{$API_KEY}:@api.octopus.energy/v1/products/" .
"AGILE-FLEX-22-11-25/electricity-tariffs/E-1R-AGILE-FLEX-22-11-25-C/standard-unit-rates/";
Your API_KEY is unique - and you'll need to check which tariff you're on.
The data is retrieved as JSON and converted:
PHP$content = file_get_contents($url);
$data = json_decode($content);
The JSON is full of entries like this:
JSON"results": [
{
"value_exc_vat": 13.6,
"value_inc_vat": 14.28,
"valid_from": "2023-11-01T22:30:00Z",
"valid_to": "2023-11-01T23:00:00Z",
"payment_method": null
},
{
"value_exc_vat": 18.4,
"value_inc_vat": 19.32,
"valid_from": "2023-11-01T22:00:00Z",
"valid_to": "2023-11-01T22:30:00Z",
"payment_method": null
},
They're newest first, so need to be reversed:
PHP$tariffs = array_reverse( $data->results );
Then it's a case of looping through them and grabbing today's data:
PHP$userTimeZone = new DateTimeZone('Europe/London');
$now = new DateTime('now', $userTimeZone);
$nowPosition = 0;
$datay = array();
$datax = array();
foreach ( $tariffs as $tariff ) {
$dateStringFrom = $tariff->valid_from;
$dateStringTo = $tariff->valid_to;
$dateTimeFrom = new DateTime($dateStringFrom, new DateTimeZone('UTC'));
$dateTimeTo = new DateTime($dateStringTo, new DateTimeZone('UTC'));
if ($now >= $dateTimeFrom && $now <= $dateTimeTo) {
$costNow = $roundedInteger = (int)round( $tariff->value_inc_vat );
$hour = intval( $dateTimeFrom->format('G') ); // No leading 0
$minute = intval( $dateTimeFrom->format('i') );
$offset = ($minute == 0) ? 0 : (($minute == 30) ? 1 : null);
$nowPosition = (2 * $hour) + $offset + 0.5;
$until = $dateTimeTo->format('H:i');
}
if ($dateTimeFrom->format('Y-m-d') == $now->format('Y-m-d')) {
$datax[] = $dateTimeFrom->format("H:i");
$cost = $roundedInteger = (int)round( $tariff->value_inc_vat );
$datay[] = $cost;
}
}
Drawing the graph uses the venerable JPGraph:
PHP$path = 'jpgraph/';
set_include_path(get_include_path() . PATH_SEPARATOR . $path);
require_once ('jpgraph/jpgraph.php');
require_once ('jpgraph/jpgraph_line.php');
require_once ('jpgraph/jpgraph_plotline.php');
// Size of graph
$width = 600;
$height = 600;
// Setup the graph
$graph = new Graph($width,$height);
$graph->SetScale("intlin");
$graph->SetMargin(35,0,45,20); // L R T B
$graph->SetUserFont('dejavu/DejaVuSansMono.ttf');
$graph->title->SetFont(FF_USERFONT,FS_NORMAL,25);
$graph->SetBox(false);
$graph->title->Set( $now->format('l') . "'s Electricity Prices\n" . $costNow . "p / kWh until {$until}" );
$graph->title->SetColor('#000');
$graph->ygrid->Show(true);
$graph->xgrid->Show(true);
$graph->xaxis->SetTickLabels( $datax );
$graph->xaxis->SetColor('#000');
$graph->yaxis->SetColor('#000');
$graph->xaxis->SetFont(FF_USERFONT, FS_NORMAL, 10);
$graph->yaxis->SetFont(FF_USERFONT, FS_NORMAL, 14);
// Just let the maximum be autoscaled
$graph->yaxis->scale->SetAutoMin(0);
// Only show up until 23:00
$graph->xaxis->scale->SetAutoMax(46);
$graph->xaxis->SetTextLabelInterval(2);
$graph->SetTickDensity(TICKD_DENSE, TICKD_DENSE);
// Create the line plot
$p1 = new LinePlot($datay);
$graph->Add($p1);
$p1->SetStepStyle();
$p1->SetColor('#000');
// Direction, position, colour@alpha, width
$l1 = new PlotLine(VERTICAL, $nowPosition, 'black@.8', 13);
// Add vertical highlight line to the plot
$graph->AddLine($l1);
// Output line
$graph->Stroke();
Next steps
I dunno? Add some details about carbon emissions? Battery stats? Let me know what you think in the comments.
Richy B. says:
It's a shame it's "authentication protected" as it doesn't give out any confidential information and it's something that could be cached either on their frontend servers or on proxy servers/CDNs: as it is, they are probably using up more computing power authenticating the API token then they would just leaving it open and catchable (especially since it changes at the same time everyday - easy enough to set cache headers).Turns out, that endpoint isn't actually restricted by API keys - so you can access it without authentication (although the Expires: setting is only a minute into the future so not that cachable)
James Singleton says:
Nice project. Battery SoC% would be useful and CI can be retrieved from https://carbonintensity.org.uk. You don't need an API key for the Agile call, it's a public open API. This works as-is. You only need the correct product and tariff code for the region.
Libosmackle says:
Are you not worried about someone nicking the batteries from your porch?
@edent says:
This may be a translation error. In British English, a porch is usually an enclosed space - rather than the external space meant by Americans. However, the batteries are securely drilled in to a solid brick wall. See https://shkspr.mobi/blog/2023/08/review-moixa-4-8kwh-solar-battery/
More comments on Mastodon.