UK Emergency Power Cuts

For the UK folk - you might not know that there is a defined schedule for emergency power cuts with areas and time periods. You can find out what area you are in on your electricity bill - it will be a letter in a square box (A - U).

There are 18 severity levels of power cuts, Level 1 is typically a few power cuts per week right up to Level 18 which is the entire National Grid is down.

You can find out more information here: https://www.nationalgrideso.com/document/13121/download

Now on to the project:

I created this JSON file, by manually entering the data from the PDF in to a spreadsheet, saving as CSV and then writing some PHP code to turn it into a nice and easy to use JSON file. Here is the file:

{"day":{"1":{"period":{"1":{"level":{"1":"A","2":"B","3":"C","4":"H","5":"T","6":"N","7":"L","8":"M","9":"K","10":"G","11":"J","12":"R","13":"D","14":"S","15":"P","16":"E","17":"U","18":"Q"}},"2":{"level":{"1":"D","2":"E","3":"G","4":"J","5":"U","6":"R","7":"P","8":"Q","9":"T","10":"A","11":"K","12":"L","13":"B","14":"H","15":"M","16":"C","17":"S","18":"N"}},"3":{"level":{"1":"B","2":"C","3":"A","4":"K","5":"S","6":"L","7":"M","8":"N","9":"J","10":"E","11":"T","12":"P","13":"G","14":"U","15":"Q","16":"D","17":"H","18":"R"}},"4":{"level":{"1":"E","2":"G","3":"D","4":"H","5":"T","6":"P","7":"Q","8":"R","9":"U","10":"B","11":"S","12":"M","13":"C","14":"J","15":"N","16":"A","17":"K","18":"L"}},"5":{"level":{"1":"C","2":"A","3":"B","4":"J","5":"U","6":"M","7":"N","8":"L","9":"S","10":"D","11":"H","12":"Q","13":"E","14":"K","15":"R","16":"G","17":"T","18":"P"}},"6":{"level":{"1":"G","2":"D","3":"E","4":"K","5":"S","6":"Q","7":"R","8":"P","9":"H","10":"C","11":"U","12":"N","13":"A","14":"T","15":"L","16":"B","17":"J","18":"M"}},"7":{"level":{"1":"B","2":"H","3":"T","4":"L","5":"A","6":"M","7":"K","8":"G","9":"J","10":"R","11":"D","12":"S","13":"P","14":"E","15":"U","16":"Q","17":"C","18":"N"}},"8":{"level":{"1":"C","2":"J","3":"U","4":"N","5":"K","6":"S","7":"T","8":"A","9":"L","10":"B","11":"H","12":"M","13":"E","14":"P","15":"G","16":"Q","17":"D","18":"R"}}}},"2":{"period":{"1":{"level":{"1":"E","2":"G","3":"D","4":"A","5":"M","6":"P","7":"Q","8":"R","9":"C","10":"K","11":"B","12":"U","13":"H","14":"L","15":"S","16":"J","17":"N","18":"T"}},"2":{"level":{"1":"H","2":"J","3":"K","4":"B","5":"N","6":"U","7":"S","8":"T","9":"M","10":"E","11":"C","12":"Q","13":"G","14":"A","15":"R","16":"D","17":"L","18":"P"}},"3":{"level":{"1":"G","2":"D","3":"E","4":"C","5":"L","6":"Q","7":"R","8":"P","9":"B","10":"J","11":"M","12":"S","13":"K","14":"N","15":"T","16":"H","17":"A","18":"U"}},"4":{"level":{"1":"J","2":"K","3":"H","4":"A","5":"M","6":"S","7":"T","8":"U","9":"N","10":"G","11":"L","12":"R","13":"D","14":"B","15":"P","16":"E","17":"C","18":"Q"}},"5":{"level":{"1":"D","2":"E","3":"G","4":"B","5":"N","6":"R","7":"P","8":"Q","9":"L","10":"H","11":"A","12":"T","13":"J","14":"C","15":"U","16":"K","17":"M","18":"S"}},"6":{"level":{"1":"K","2":"H","3":"J","4":"C","5":"L","6":"T","7":"U","8":"S","9":"A","10":"D","11":"N","12":"P","13":"E","14":"M","15":"Q","16":"G","17":"B","18":"R"}},"7":{"level":{"1":"E","2":"A","3":"N","4":"P","5":"D","6":"Q","7":"C","8":"K","9":"B","10":"U","11":"H","12":"L","13":"S","14":"J","15":"T","16":"M","17":"G","18":"R"}},"8":{"level":{"1":"G","2":"B","3":"M","4":"R","5":"C","6":"L","7":"E","8":"Q","9":"A","10":"D","11":"P","12":"J","13":"S","14":"K","15":"N","16":"T","17":"H","18":"U"}}}},"3":{"period":{"1":{"level":{"1":"J","2":"K","3":"H","4":"D","5":"Q","6":"S","7":"T","8":"U","9":"G","10":"A","11":"E","12":"L","13":"B","14":"P","15":"M","16":"C","17":"R","18":"N"}},"2":{"level":{"1":"B","2":"C","3":"A","4":"E","5":"R","6":"L","7":"M","8":"N","9":"Q","10":"J","11":"G","12":"T","13":"K","14":"D","15":"U","16":"H","17":"P","18":"S"}},"3":{"level":{"1":"K","2":"H","3":"J","4":"G","5":"P","6":"T","7":"U","8":"S","9":"E","10":"C","11":"Q","12":"M","13":"A","14":"R","15":"N","16":"B","17":"D","18":"L"}},"4":{"level":{"1":"C","2":"A","3":"B","4":"D","5":"Q","6":"M","7":"N","8":"L","9":"R","10":"K","11":"P","12":"U","13":"H","14":"E","15":"S","16":"J","17":"G","18":"T"}},"5":{"level":{"1":"H","2":"J","3":"K","4":"E","5":"R","6":"U","7":"S","8":"T","9":"P","10":"N","11":"D","12":"N","13":"C","14":"G","15":"L","16":"A","17":"Q","18":"M"}},"6":{"level":{"1":"A","2":"B","3":"C","4":"G","5":"P","6":"N","7":"L","8":"M","9":"D","10":"H","11":"R","12":"S","13":"J","14":"Q","15":"T","16":"K","17":"E","18":"U"}},"7":{"level":{"1":"H","2":"D","3":"R","4":"T","5":"J","6":"S","7":"G","8":"A","9":"E","10":"L","11":"B","12":"P","13":"M","14":"C","15":"N","16":"K","17":"U","18":"Q"}},"8":{"level":{"1":"K","2":"E","3":"Q","4":"U","5":"G","6":"P","7":"J","8":"T","9":"D","10":"H","11":"S","12":"C","13":"M","14":"A","15":"R","16":"N","17":"B","18":"L"}}}},"4":{"period":{"1":{"level":{"1":"L","2":"M","3":"N","4":"S","5":"J","6":"C","7":"A","8":"B","9":"U","10":"R","11":"T","12":"G","13":"P","14":"H","15":"D","16":"Q","17":"K","18":"E"}},"2":{"level":{"1":"P","2":"Q","3":"R","4":"T","5":"K","6":"G","7":"D","8":"E","9":"J","10":"L","11":"U","12":"A","13":"M","14":"S","15":"B","16":"N","17":"H","18":"C"}},"3":{"level":{"1":"M","2":"N","3":"L","4":"U","5":"H","6":"A","7":"B","8":"C","9":"T","10":"Q","11":"J","12":"D","13":"R","14":"K","15":"E","16":"P","17":"S","18":"G"}},"4":{"level":{"1":"Q","2":"R","3":"P","4":"S","5":"J","6":"D","7":"E","8":"G","9":"K","10":"M","11":"H","12":"B","13":"N","14":"T","15":"C","16":"L","17":"U","18":"A"}},"5":{"level":{"1":"N","2":"L","3":"M","4":"T","5":"K","6":"B","7":"C","8":"A","9":"H","10":"P","11":"S","12":"E","13":"Q","14":"U","15":"G","16":"R","17":"J","18":"D"}},"6":{"level":{"1":"R","2":"P","3":"Q","4":"U","5":"H","6":"E","7":"G","8":"D","9":"S","10":"N","11":"K","12":"C","13":"L","14":"J","15":"A","16":"M","17":"T","18":"B"}},"7":{"level":{"1":"M","2":"S","3":"J","4":"A","5":"L","6":"B","7":"U","8":"R","9":"T","10":"G","11":"P","12":"H","13":"D","14":"Q","15":"K","16":"E","17":"N","18":"C"}},"8":{"level":{"1":"N","2":"T","3":"K","4":"C","5":"U","6":"H","7":"J","8":"L","9":"A","10":"M","11":"S","12":"B","13":"Q","14":"D","15":"R","16":"E","17":"P","18":"G"}}}},"5":{"period":{"1":{"level":{"1":"Q","2":"R","3":"P","4":"L","5":"B","6":"D","7":"E","8":"G","9":"N","10":"U","11":"M","12":"K","13":"S","14":"A","15":"H","16":"T","17":"C","18":"J"}},"2":{"level":{"1":"S","2":"T","3":"U","4":"M","5":"C","6":"K","7":"H","8":"J","9":"B","10":"Q","11":"N","12":"E","13":"R","14":"L","15":"G","16":"P","17":"A","18":"D"}},"3":{"level":{"1":"R","2":"P","3":"Q","4":"N","5":"A","6":"E","7":"G","8":"D","9":"M","10":"T","11":"B","12":"H","13":"U","14":"C","15":"J","16":"S","17":"L","18":"K"}},"4":{"level":{"1":"T","2":"U","3":"S","4":"L","5":"B","6":"H","7":"J","8":"K","9":"C","10":"R","11":"A","12":"G","13":"P","14":"M","15":"D","16":"Q","17":"N","18":"E"}},"5":{"level":{"1":"P","2":"Q","3":"R","4":"M","5":"C","6":"G","7":"D","8":"E","9":"A","10":"S","11":"L","12":"J","13":"T","14":"N","15":"K","16":"U","17":"B","18":"H"}},"6":{"level":{"1":"U","2":"S","3":"T","4":"N","5":"A","6":"J","7":"K","8":"H","9":"L","10":"P","11":"C","12":"D","13":"Q","14":"B","15":"E","16":"R","17":"M","18":"G"}},"7":{"level":{"1":"Q","2":"L","3":"C","4":"D","5":"P","6":"E","7":"N","8":"U","9":"M","10":"K","11":"S","12":"A","13":"H","14":"T","15":"J","16":"R","17":"G","18":"B"}},"8":{"level":{"1":"R","2":"M","3":"B","4":"G","5":"N","6":"A","7":"Q","8":"E","9":"L","10":"P","11":"D","12":"T","13":"H","14":"U","15":"C","16":"J","17":"S","18":"K"}}}},"6":{"period":{"1":{"level":{"1":"T","2":"U","3":"S","4":"P","5":"E","6":"H","7":"J","8":"K","9":"R","10":"L","11":"Q","12":"A","13":"M","14":"D","15":"B","16":"N","17":"G","18":"C"}},"2":{"level":{"1":"M","2":"N","3":"L","4":"Q","5":"G","6":"A","7":"B","8":"C","9":"E","10":"T","11":"R","12":"J","13":"U","14":"P","15":"K","16":"S","17":"D","18":"H"}},"3":{"level":{"1":"U","2":"S","3":"T","4":"R","5":"D","6":"J","7":"K","8":"H","9":"Q","10":"N","11":"E","12":"B","13":"L","14":"G","15":"C","16":"M","17":"P","18":"A"}},"4":{"level":{"1":"N","2":"L","3":"M","4":"P","5":"E","6":"B","7":"C","8":"A","9":"G","10":"U","11":"D","12":"K","13":"S","14":"Q","15":"H","16":"T","17":"R","18":"J"}},"5":{"level":{"1":"S","2":"T","3":"U","4":"Q","5":"G","6":"K","7":"H","8":"J","9":"D","10":"M","11":"P","12":"C","13":"N","14":"R","15":"A","16":"L","17":"E","18":"B"}},"6":{"level":{"1":"L","2":"M","3":"N","4":"R","5":"D","6":"C","7":"A","8":"B","9":"P","10":"S","11":"G","12":"H","13":"T","14":"E","15":"J","16":"U","17":"Q","18":"K"}},"7":{"level":{"1":"S","2":"P","3":"G","4":"H","5":"T","6":"K","7":"R","8":"L","9":"Q","10":"A","11":"M","12":"D","13":"B","14":"N","15":"C","16":"J","17":"U","18":"E"}},"8":{"level":{"1":"U","2":"Q","3":"E","4":"J","5":"R","6":"D","7":"T","8":"P","9":"K","10":"S","11":"H","12":"N","13":"B","14":"L","15":"G","16":"C","17":"M","18":"A"}}}},"7":{"period":{"1":{"level":{"1":"A","2":"B","3":"C","4":"L","5":"N","6":"M","7":"E","8":"G","9":"D","10":"Q","11":"R","12":"P","13":"K","14":"H","15":"J","16":"U","17":"S","18":"T"}},"2":{"level":{"1":"D","2":"E","3":"G","4":"P","5":"Q","6":"R","7":"J","8":"K","9":"H","10":"T","11":"U","12":"S","13":"N","14":"L","15":"M","16":"C","17":"A","18":"B"}},"3":{"level":{"1":"H","2":"J","3":"K","4":"S","5":"T","6":"U","7":"M","8":"N","9":"L","10":"B","11":"C","12":"A","13":"R","14":"P","15":"Q","16":"G","17":"D","18":"E"}},"4":{"level":{"1":"L","2":"M","3":"N","4":"A","5":"C","6":"B","7":"Q","8":"R","9":"P","10":"E","11":"G","12":"D","13":"U","14":"S","15":"T","16":"K","17":"H","18":"J"}},"5":{"level":{"1":"P","2":"Q","3":"R","4":"D","5":"E","6":"G","7":"T","8":"U","9":"S","10":"J","11":"K","12":"H","13":"C","14":"A","15":"B","16":"N","17":"L","18":"M"}},"6":{"level":{"1":"S","2":"T","3":"U","4":"H","5":"J","6":"K","7":"B","8":"C","9":"A","10":"M","11":"N","12":"L","13":"G","14":"D","15":"E","16":"R","17":"P","18":"Q"}},"7":{"level":{"1":"P","2":"E","3":"R","4":"C","5":"B","6":"A","7":"L","8":"G","9":"D","10":"Q","11":"N","12":"M","13":"K","14":"H","15":"J","16":"U","17":"S","18":"T"}}}}}}

I then wrote some PHP code that you can stick on a webserver to return some JSON that can be used to populate a sensor.

<?php
date_default_timezone_set("Europe/London");
ob_start();
$APP['area'] = "A";
$APP['period'] = -1;
$APP['level'] = 1;
$APP['debug'] = false;

if (isset($_GET['area'])) {
	$q = $_GET['area'];
	if (strlen($q) == 1) {
		$APP['area'] = strtoupper($q);
	}
}

if (isset($_GET['period'])) {
	$q = $_GET['period'];
	if (is_numeric($q) && (($q > 0 && $q < 9) || $q == -1)) {
		$APP['period'] = $q;
	}
} else {
	$APP['period'] = -1;
}

if (isset($_GET['level'])) {
	$q = $_GET['level'];
	if (is_numeric($q) && ($q > 0 && $q < 19)) {
		$APP['level'] = $q;
	}
}

// load data
$data = json_decode(file_get_contents("./data/eso_schedule.json"),true);

// if we are looking for a period then we want to return a true or a false
if ($APP['period'] !== -1) {
	$out['query']['area'] = $APP['area'];
	$out['query']['period'] = $APP['period'];
	$out['query']['level'] = $APP['level'];
	$out['result'] = checkPeriod($APP['period']);
	header("content-type: application/json");
	echo json_encode($out);
	exit;
}
$q = nextPeriod();
$out['query']['area'] = $APP['area'];
$out['query']['level'] = $APP['level'];
$out['result'] = $q;
$today = date('N');
$TS = mktime(0,0,0); //set timestamp to midnight today
$diff = 0;
if ($today < $q['day']) {
	$diff = $q['day'] - $today;
} elseif ($today > $q['day']) {
	$diff = $today + $q['day'];
}
if ($diff) { $TS = $TS + (86400 * $diff); } //add number of days to today
$p = explode(":",timePeriods($q['period'])); //get the hours and minutes of the start of the period
$d = mktime($p[0],$p[1],0,date('m',$TS),date('j',$TS),date('Y',$TS)); //create timestamp
$out['result']['timestamp'] = date('c',$d);
header("content-type: application/json");
echo json_encode($out);
exit;

function checkPeriod($period) {
global $data, $APP;
$day = date('N');
$f = false;
$i = 1;
while ($i < $APP['level'] + 1) {
	if ($data['day'][$day]['period'][$period]['level'][$i] == $APP['area']) {
		$f = true;
		break;
	}
$i++;
}
return $f;
}

function nextPeriod() {
global $APP, $data;
$data['day'][8] = $data['day'][1];
$data['day'][9] = $data['day'][2];
$data['day'][10] = $data['day'][3];
$data['day'][11] = $data['day'][4];
$data['day'][12] = $data['day'][5];
$data['day'][13] = $data['day'][6];
$data['day'][14] = $data['day'][7];
$today = date('N');
$f = false;
$i = $today;
while($i < 14) { // days
	$p = 1;
	while ($p < 9) { //periods
		$l = 1;
		while ($l < $APP['level'] + 1) { // levels
			if ($data['day'][$i]['period'][$p]['level'][$l] == $APP['area']) {
				$f = true;
				break;
			}
			if ($APP['debug']) {
				echo "\nDay: {$i}, Period: {$p}, Level: {$l} == {$data['day'][$i]['period'][$p]['level'][$l]}";
			}
			$l++;
		}
		if ($f) {
			break;
		}
		$p++;
	}
	if ($f) {
		break;
	}
	$i++;
}
	if ($i > 7) { $i = $i - 7; }
	$ret['day'] = $i;
	$ret['period'] = $p;
	$ret['level'] = $l;
	return $ret;
}

function timePeriods($period) {
$p =
[
"06:30",
"09:30",
"12:30",
"15:30",
"18:30",
"21:30",
"00:30",
"03:30",
];
return $p[$period-1];
}
?>

The way I use this: I am in Area A, and for now at least I am interested in level 1 disconnections. So I would use https://my-server-name/eso_schedule.php?area=a&level=1 as the REST resource and I would receive something back like:

{"query":{"area":"A","level":"1"},"result":{"day":3,"period":6,"level":1,"timestamp":"2022-10-26T21:30:00+01:00"}}

which tells me that IF they have to implement the emergency power cuts, my next one would be scheduled tomorrow (Wednesday 26th) at 9:30pm.

Yes it’s annoying that we might actually need this information over this winter, but I have done the hard work for you, so at least you can be prepared.

Note:
Because the schedule is done on increasing levels of severity, if I looked for a level 8 severity:

eso_schedule.php?area=a&level=8

You will notice that in the result:

{"query":{"area":"A","level":"8"},"result":{"day":"2","period":1,"level":4,"timestamp":"2022-10-25T06:30:00+01:00"}}

It says level 4 - because in this time period it will be a scheduled powercut from level 4 right through to level 18

4 Likes

Thanks for taking time to do this!

1 Like

This looks useful thanks!

Might be worth having this on GitHub. Could even have it build an automated docker image that will could pull down to run this easily :slight_smile:

1 Like

Things I would love to do, but sadly I have managed to not learn Github…
If someone else wants to - I have no problems with that at all.

Update:

<?php
date_default_timezone_set("Europe/London");
ob_start();
$APP['area'] = "A";
$APP['period'] = -1;
$APP['level'] = 1;
$APP['debug'] = false;

if (isset($_GET['area'])) {
	$q = $_GET['area'];
	if (strlen($q) == 1) {
		$APP['area'] = strtoupper($q);
	}
}

if (isset($_GET['period'])) {
	$q = $_GET['period'];
	if (is_numeric($q) && (($q > 0 && $q < 9) || $q == -1)) {
		$APP['period'] = $q;
	}
} else {
	$APP['period'] = -1;
}

if (isset($_GET['level'])) {
	$q = $_GET['level'];
	if (is_numeric($q) && (($q > 0 && $q < 19) || $q == -1)) {
		$APP['level'] = $q;
	}
}

// load data
$data = json_decode(file_get_contents("./data/eso_schedule.json"),true);

// if we are looking for a period then we want to return a true or a false
if ($APP['period'] !== -1) {
	$out['query']['area'] = $APP['area'];
	$out['query']['period'] = $APP['period'];
	$out['query']['level'] = $APP['level'];
	$out['result'] = checkPeriod($APP['period']);
	header("content-type: application/json");
	echo json_encode($out);
	exit;
}
if ($APP['level'] > 0) {
$q = stepThroughPeriods(nextPeriod());
$out['query']['area'] = $APP['area'];
$out['query']['level'] = $APP['level'];
$out['result'] = $q;
$today = date('N');
$TS = mktime(0,0,0);
$diff = 0;
if ($today < $q['day']) {
	$diff = $q['day'] - $today;
} elseif ($today > $q['day']) {
	$diff = $today + $q['day'];
}
if ($diff) { $TS = $TS + (86400 * $diff); }
$p = explode(":",timePeriods($q['period']));
$d = mktime($p[0],$p[1],0,date('m',$TS),date('j',$TS),date('Y',$TS));
$out['result']['timestamp'] = date('c',$d);
header("content-type: application/json");
echo json_encode($out);
exit;
} else {
    // we want ALL the periods
$APP['level'] = 1;
$out['query']['area'] = $APP['area'];
$out['query']['level'] = "ALL";
$today = date('N');
$TS = mktime(0,0,0);
while ($APP['level'] < 19) {
    $q = stepThroughPeriods(nextPeriod());
    //print_r($q);
    $diff = 0;
    if ($today < $q['day']) {
	    $diff = $q['day'] - $today;
    } elseif ($today > $q['day']) {
	    $diff = $today + $q['day'];
    }
    if ($diff) { $TSS = $TS + (86400 * $diff); }
    $p = explode(":",timePeriods($q['period']));
    $d = mktime($p[0],$p[1],0,date('m',$TSS),date('j',$TSS),date('Y',$TSS));
    $q['timestamp'] = date('c',$d);
    $out['result']['level'][$APP['level']] = $q;
$APP['level']++;
}
header("content-type: application/json");
echo json_encode($out);
exit;
}

function checkPeriod($period) {
global $data, $APP;
$day = date('N');
$f = false;
$i = 1;
while ($i < $APP['level'] + 1) {
	if ($data['day'][$day]['period'][$period]['level'][$i] == $APP['area']) {
		$f = true;
		break;
	}
$i++;
}
return $f;
}

function nextPeriod() {
global $APP, $data;
$data['day'][8] = $data['day'][1];
$data['day'][9] = $data['day'][2];
$data['day'][10] = $data['day'][3];
$data['day'][11] = $data['day'][4];
$data['day'][12] = $data['day'][5];
$data['day'][13] = $data['day'][6];
$data['day'][14] = $data['day'][7];
$today = date('N');
$f = false;
$i = $today;
while($i < 14) { // days
	$p = 1;
	while ($p < 9) { //periods
		$l = 1;
		while ($l < $APP['level'] + 1) { // levels
			if ($data['day'][$i]['period'][$p]['level'][$l] == $APP['area']) {
				$f = true;
				break;
			}
			if ($APP['debug']) {
				echo "\nDay: {$i}, Period: {$p}, Level: {$l} == {$data['day'][$i]['period'][$p]['level'][$l]}";
			}
			$l++;
		}
		if ($f) {
			break;
		}
		$p++;
	}
	if ($f) {
		break;
	}
	$i++;
}
	if ($i > 7) { $i = $i - 7; }
	$ret['day'] = $i;
	$ret['period'] = $p;
	$ret['level'] = $l;
	return $ret;
}

function timePeriods($period) {
$p =
[
"00:30",
"03:30",
"06:30",
"09:30",
"12:30",
"15:30",
"18:30",
"21:30",
];
return $p[$period-1];
}

function stepThroughPeriods($d) {
global $data;
$now = time();
$today = date('N');
$TS = mktime(0,0,0);
if ($today !== $d['day']) { return $d; }
$i = 1;
while ($i < 8) {
    $p = explode(":",timePeriods($i));
    $dt = mktime($p[0],$p[1],0,date('m',$TS),date('j',$TS),date('Y',$TS));
    if ($dt > $now) { break; }
    //echo "dt: " .date('c',$dt) ." less than " .date('c',$now) ."\n";
$i++;
}
//echo "Matched: " .date('c',$dt) ." || period: {$i}\n";
$d['period'] = $i;
return $d;
}
?>

Fixes:

  1. Now actually returns the next period if it’s today, rather than only the first period of the day.
  2. The time periods have been updated because they were changed in 2019. ( https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/995049/esec-guidance.pdf )
  3. You can now pass level=-1 to get ALL the levels in a single call eg:

eso_schedule.php?area=a&level=-1

returns:

{
  "query": {
    "area": "A",
    "level": "ALL"
  },
  "result": {
    "level": {
      "1": {
        "day": 7,
        "period": 1,
        "level": 1,
        "timestamp": "2022-10-30T00:30:00+01:00"
      },
      "2": {
        "day": 7,
        "period": 1,
        "level": 1,
        "timestamp": "2022-10-30T00:30:00+01:00"
      },
      "3": {
        "day": 7,
        "period": 1,
        "level": 1,
        "timestamp": "2022-10-30T00:30:00+01:00"
      },
      "4": {
        "day": "4",
        "period": 8,
        "level": 4,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "5": {
        "day": "4",
        "period": 8,
        "level": 4,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "6": {
        "day": "4",
        "period": 8,
        "level": 6,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "7": {
        "day": "4",
        "period": 8,
        "level": 7,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "8": {
        "day": "4",
        "period": 8,
        "level": 7,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "9": {
        "day": "4",
        "period": 8,
        "level": 7,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "10": {
        "day": "4",
        "period": 8,
        "level": 7,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "11": {
        "day": "4",
        "period": 8,
        "level": 7,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "12": {
        "day": "4",
        "period": 8,
        "level": 7,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "13": {
        "day": "4",
        "period": 8,
        "level": 7,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "14": {
        "day": "4",
        "period": 8,
        "level": 7,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "15": {
        "day": "4",
        "period": 8,
        "level": 7,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "16": {
        "day": "4",
        "period": 8,
        "level": 7,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "17": {
        "day": "4",
        "period": 8,
        "level": 7,
        "timestamp": "2022-10-30T21:30:00+00:00"
      },
      "18": {
        "day": "4",
        "period": 8,
        "level": 7,
        "timestamp": "2022-10-30T21:30:00+00:00"
      }
    }
  }
}

So you don’t need to make multiple calls for different levels.

Here is my config:

sensor:

 - platform: rest
   name: ESO Schedule
   json_attributes:
     - result
   resource: https://myserver/eso/eso_schedule.php?area=a&level=-1
   value_template: "OK"
   force_update: true
   unique_id: eso-6283faa0-80ed-4401-9d00-0b6ded2427b9
   scan_interval: 1800

template:

 - trigger:
     - platform: state
       entity_id: sensor.eso_schedule
   sensor:
     - name: ESO Level 1
       unique_id: eso-l1-8fa79d4a-630e-4df3-b00d-ea96fe6e4154
       state: >-
          {{ state_attr('sensor.eso_schedule','result')['level']['1']['timestamp'] }}
       device_class: timestamp
       icon: mdi:transmission-tower-off
     - name: ESO Level 2
       unique_id: eso-l2-8fa79d4a-630e-4df3-b00d-ea96fe6e4154
       state: >-
          {{ state_attr('sensor.eso_schedule','result')['level']['2']['timestamp'] }}
       device_class: timestamp
       icon: mdi:transmission-tower-off

Hope this helps someone.

TIL, we have scheduled power cuts in the UK!

I’ll be honest, they are very rare around here, but perhaps other bits of the country have them more often. That said, they do happen often enough, and Vodafone’s stock router is bad enough, that I have a UPS protecting key equipment from uncontrolled restarts which has really helped improve the stability of my overall network.

Thanks anyway, OP. Learn something new every day!

1 Like

It’s not so much that we have them, it’s that the Government and the utility companies are prepared to impose them, in the event that because of our over reliance on Russian origin gas - to run our power stations, we find ourselves without enough gas to power the entire grid, and thus to prevent the entire grid from collapsing, then they can shut down sections of the grid, on a schedule.

According to the Government, we “definitely won’t have power cuts” - so it’s probably just as well to prepare for them…

Latest update:
Major refactor to cope with level -1 returning the next period today instead of the next period of disconnection:

<?php
date_default_timezone_set("Europe/London");
ob_start();
$APP['area'] = "A";
$APP['period'] = -1;
$APP['level'] = 1;
$APP['debug'] = false;

if (isset($_GET['area'])) {
	$q = $_GET['area'];
	if (strlen($q) == 1) {
		$APP['area'] = strtoupper($q);
	}
}

if (isset($_GET['period'])) {
	$q = $_GET['period'];
	if (is_numeric($q) && (($q > 0 && $q < 9) || $q == -1)) {
		$APP['period'] = $q;
	}
} else {
	$APP['period'] = -1;
}

if (isset($_GET['level'])) {
	$q = $_GET['level'];
	if (is_numeric($q) && (($q > 0 && $q < 19) || $q == -1)) {
		$APP['level'] = $q;
	}
}

// load data
$data = json_decode(file_get_contents("./data/eso_schedule.json"),true);
$data['periods'] =
[
"","00:30","03:30","06:30","09:30",
"12:30","15:30","18:30","21:30"];
$data['days'] = [
"","Monday","Tuesday","Wednesday",
"Thursday","Friday","Saturday","Sunday"];

// if we are looking for a period then we want to return a true or a false
if ($APP['period'] !== -1) {
	$out['query']['area'] = $APP['area'];
	$out['query']['period'] = $APP['period'];
	$out['query']['level'] = $APP['level'];
	$out['result'] = checkPeriod($APP['period']);
	$out['result']['stime'] = time();
	header("content-type: application/json");
	echo json_encode($out);
	exit;
}
// if we are looking for all priods up to a specific level - we return an array
if ($APP['level'] > 0) {
$q = esoBuildTable();
$out['query']['area'] = $APP['area'];
$out['query']['level'] = $APP['level'];
$out['result'] = $q[0];
$out['result']['stime'] = time();
header("content-type: application/json");
echo json_encode($out);
exit;
} else {
    // we want ALL the periods for ALL the levels
$APP['level'] = 1;
$out['query']['area'] = $APP['area'];
$out['query']['level'] = "ALL";

while ($APP['level'] < 19) {
    $q = esoBuildTable();
    $out['result']['level'][$APP['level']] = $q[0];
$APP['level']++;
}
$out['result']['stime'] = time();
header("content-type: application/json");
echo json_encode($out);
exit;
}

function checkPeriod($period) {
global $data, $APP;
$day = date('N');
$f = false;
$i = 1;
while ($i < $APP['level'] + 1) {
	if ($data['day'][$day]['period'][$period]['level'][$i] == $APP['area']) {
		$f = true;
		break;
	}
$i++;
}
return $f;
}


function esoBuildTable() {
global $APP,$data;

$li = 1;
$table = [];
$now = time();
while($li < ($APP['level'] +1)) {
	$di = 1;
	while($di < 8) {
		$pi = 1;
		while($pi < 9) {
			if ($data['day'][$di]['period'][$pi]['level'][$li] == $APP['area']) {
				// add to table
				//esoDEBUG("Found: Day[{$di}], Period: {$pi} on di: {$di}");
				$ts = strtotime("{$data['days'][$di]} {$data['periods'][$pi]}");
				//esoDEBUG(date('c',$ts));
				$t['level'] = $li;
				$t['period'] = $pi;
				$t['area'] = $APP['area'];
				$t['day'] = $di;
				$t['timestamp'] = date('c',$ts);
				$t['ends'] = date('c',$ts + (3600 * 3));
				$ti = date('YmdGi',$ts);
				if ($ts + ((3600 * 3) - 300) > $now && !array_key_exists($ti,$table)) { $table[$ti] = $t; }
			}
			$pi++;
		}
		$di++;
	}
	$li++;
}
usort($table, function ($item1, $item2) {
    return $item1['timestamp'] <=> $item2['timestamp'];
});
return $table;
}

function esoDEBUG($str) {
global $APP;
if ($APP['debug']) {
	echo "\nDEBUG:: {$str}";
}
}	
?>

Template:

 - trigger:
     - platform: state
       entity_id: sensor.eso_schedule
   sensor:
     - name: ESO Level 1
       unique_id: eso-l1-8fa79d4a-630e-4df3-b00d-ea96fe6e4154
       state: >-
          {{ state_attr('sensor.eso_schedule','result')['level']['1']['timestamp'] }}
       device_class: timestamp
       icon: mdi:transmission-tower-off
       attributes:
         period: "{{ state_attr('sensor.eso_schedule','result')['level']['1']['period'] }}"
         level: "{{ state_attr('sensor.eso_schedule','result')['level']['1']['level'] }}"
         until: "{{ state_attr('sensor.eso_schedule','result')['level']['1']['ends'] }}"
     - name: ESO Level 2
       unique_id: eso-l2-8fa79d4a-630e-4df3-b00d-ea96fe6e4154
       state: >-
          {{ state_attr('sensor.eso_schedule','result')['level']['2']['timestamp'] }}
       device_class: timestamp
       icon: mdi:transmission-tower-off
       attributes:
         period: "{{ state_attr('sensor.eso_schedule','result')['level']['2']['period'] }}"
         level: "{{ state_attr('sensor.eso_schedule','result')['level']['2']['level'] }}"
         until: "{{ state_attr('sensor.eso_schedule','result')['level']['2']['ends'] }}"
     - name: ESO Level 3
       unique_id: eso-l3-8fa79d4a-630e-4df3-b00d-ea96fe6e4154
       state: >-
          {{ state_attr('sensor.eso_schedule','result')['level']['3']['timestamp'] }}
       device_class: timestamp
       icon: mdi:transmission-tower-off
       attributes:
         period: "{{ state_attr('sensor.eso_schedule','result')['level']['3']['period'] }}"
         level: "{{ state_attr('sensor.eso_schedule','result')['level']['3']['level'] }}"
         until: "{{ state_attr('sensor.eso_schedule','result')['level']['3']['ends'] }}"
     - name: ESO Level 4
       unique_id: eso-l4-8fa79d4a-630e-4df3-b00d-ea96fe6e4154
       state: >-
          {{ state_attr('sensor.eso_schedule','result')['level']['4']['timestamp'] }}
       device_class: timestamp
       icon: mdi:transmission-tower-off
       attributes:
         period: "{{ state_attr('sensor.eso_schedule','result')['level']['4']['period'] }}"
         level: "{{ state_attr('sensor.eso_schedule','result')['level']['4']['level'] }}"
         until: "{{ state_attr('sensor.eso_schedule','result')['level']['4']['ends'] }}"
     - name: ESO Level 5
       unique_id: eso-l5-8fa79d4a-630e-4df3-b00d-ea96fe6e4154
       state: >-
          {{ state_attr('sensor.eso_schedule','result')['level']['5']['timestamp'] }}
       device_class: timestamp
       icon: mdi:transmission-tower-off
       attributes:
         period: "{{ state_attr('sensor.eso_schedule','result')['level']['5']['period'] }}"
         level: "{{ state_attr('sensor.eso_schedule','result')['level']['5']['level'] }}"
         until: "{{ state_attr('sensor.eso_schedule','result')['level']['5']['ends'] }}"
1 Like

I’m thinking, what if this was made into a public Google Calendar?

There are 18 different areas, and 18 different severity levels.
So it would need to be 324 calendars…

This could be one calendar, as you can filter entries using search as long as the naming is consistent.

The calendar idea is a good one. The script could be used to create an ICS that could be hosted on a public service.

Using the ICS Calendar Integration we could then import and filter :slight_smile:

I’m going to look in to it, there will definitely need to be multiple calendars though. 17 at least, because of the severity levels. They are additive, so if you are off between 12:30pm - 3:30pm on a Wednesday in Area C, at level 4, you are off on level 5-18 too.

There is no point in creating a calendar for level 18, because there result is simply true, you are off in every area, for every time period (IE the whole of the UK is blacked out)

3 Likes