Skip to content

Instantly share code, notes, and snippets.

@Mr-Octo
Last active January 8, 2024 17:06
Show Gist options
  • Save Mr-Octo/83a2ff80d99599599def737197829b7e to your computer and use it in GitHub Desktop.
Save Mr-Octo/83a2ff80d99599599def737197829b7e to your computer and use it in GitHub Desktop.
Octopus Energy Saving Sessions

This flow finds and automatically joins Octopus Energy Saving Sessions.

You can also use it to carry out actions when a saving session starts or ends, via the switch node.

Enter your Account Number and API Key (found here) into the Octopus Energy GraphQL node properties (or set a default in the sub flow env properties).

Requires: node-red-contrib-graphql

If you haven't already joined Octopus Energy, seize the opportunity through my referral link https://share.octopus.energy/blue-beach-240. By using this link, both of us will receive a £50 credit. Already an Octopus Energy Customer? You can still add the referral code by contacting them, as long as you signed up directly without any referral code. Let's both enjoy a £50 credit!

[
{
"id": "8c10a679bfd2386e",
"type": "subflow",
"name": "Octopus Energy GraphQL",
"info": "",
"category": "",
"in": [
{
"x": 80,
"y": 140,
"wires": [
{
"id": "ab8b6368f466dd5a"
}
]
}
],
"out": [
{
"x": 770,
"y": 140,
"wires": [
{
"id": "799223ca9ce61160",
"port": 0
}
]
},
{
"x": 480,
"y": 580,
"wires": [
{
"id": "4c8b3548cc35074b",
"port": 0
}
]
}
],
"env": [
{
"name": "ACCOUNTNUMBER",
"type": "str",
"value": "",
"ui": {
"icon": "font-awesome/fa-address-card-o",
"label": {
"en-US": "Account Number"
},
"type": "input",
"opts": {
"types": [
"str"
]
}
}
},
{
"name": "APIKEY",
"type": "cred",
"ui": {
"icon": "font-awesome/fa-key",
"label": {
"en-US": "API Key"
},
"type": "input",
"opts": {
"types": [
"str",
"cred"
]
}
}
}
],
"meta": {},
"credentials": {
"APIKEY": ""
},
"color": "#D8BFD8",
"inputLabels": [
"GraphQL query"
],
"outputLabels": [
"GraphQL output",
"GraphQL error"
],
"status": {
"x": 440,
"y": 840,
"wires": [
{
"id": "6c5a11d5aa1aa28e",
"port": 0
}
]
}
},
{
"id": "89c2bb38024725e7",
"type": "graphql",
"z": "8c10a679bfd2386e",
"name": "obtainKrakenToken",
"graphql": "920ed6a5c25f475c",
"format": "text",
"template": "mutation krakenTokenAuthentication($apikey: String!) {\n obtainKrakenToken(input: {APIKey: $apikey}) {\n token\n }\n}",
"syntax": "plain",
"token": "",
"showDebug": false,
"x": 480,
"y": 400,
"wires": [
[
"9e176a124108b191"
],
[
"178a5d28b4fe2aab"
]
]
},
{
"id": "45fc3f5515b6658b",
"type": "function",
"z": "8c10a679bfd2386e",
"name": "Set API Key",
"func": "if (!msg.variables) {\n msg.variables = {};\n}\n\nmsg.variables.apikey = env.get(\"APIKEY\")\n\nmsg.payload={}\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 230,
"y": 400,
"wires": [
[
"89c2bb38024725e7"
]
]
},
{
"id": "9e176a124108b191",
"type": "function",
"z": "8c10a679bfd2386e",
"name": "Set krakenToken flow variable",
"func": "flow.set(\"octopus.graphql.krakenToken\", msg.payload.graphql.obtainKrakenToken.token)\n\nif(msg.refreshToken) {\n delete msg.refreshToken\n}\n\ndelete msg.payload.graphql\n\nvar status = { fill: \"yellow\", text: \"Token obtained\" }\n\nreturn [msg, status]",
"outputs": 2,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 790,
"y": 400,
"wires": [
[
"b22be99526d4a842"
],
[
"b5f209b32c478859"
]
],
"outputLabels": [
"Query",
"Status"
]
},
{
"id": "799223ca9ce61160",
"type": "graphql",
"z": "8c10a679bfd2386e",
"name": "Run GraphQL",
"graphql": "920ed6a5c25f475c",
"format": "handlebars",
"template": "{{query}}",
"syntax": "mustache",
"token": "",
"showDebug": false,
"x": 520,
"y": 140,
"wires": [
[
"beb4a5a15a77d3df"
],
[
"07b8cede1bb55008"
]
]
},
{
"id": "ab8b6368f466dd5a",
"type": "function",
"z": "8c10a679bfd2386e",
"name": "Prepare for GraphQL",
"func": "var status\n\nif (!flow.get(\"octopus.graphql.krakenToken\")) {\n var newMsg = {}\n newMsg.refreshToken = true\n newMsg.oldMsg = msg\n\n status = { fill: \"purple\", text: \"Obtaining token\" }\n\n return [null, newMsg, status]\n}\n\nif(msg.oldMsg) {\n msg = msg.oldMsg\n}\n\nmsg.customHeaders = {}\nmsg.customHeaders.Authorization = flow.get(\"octopus.graphql.krakenToken\")\n\nif (!msg.variables) {\n msg.variables = {};\n}\n\nmsg.variables.accountNumber = env.get(\"ACCOUNTNUMBER\")\n\nif(!msg.payload) {\n msg.payload = {}\n}\nmsg.payload.graphql={}\n\nstatus = { fill: \"yellow\", text: \"Running query\" }\n\nreturn [msg, null, status];",
"outputs": 3,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 260,
"y": 140,
"wires": [
[
"799223ca9ce61160"
],
[
"da8e4250054dc797"
],
[
"bad44dd40a123091"
]
],
"inputLabels": [
"Query"
],
"outputLabels": [
"GraphQL",
"Get Token",
"Status"
]
},
{
"id": "be9497eea5625c3f",
"type": "link in",
"z": "8c10a679bfd2386e",
"name": "Obtain Token",
"links": [
"436150eaf14baa43",
"da8e4250054dc797"
],
"x": 75,
"y": 400,
"wires": [
[
"45fc3f5515b6658b"
]
]
},
{
"id": "da8e4250054dc797",
"type": "link out",
"z": "8c10a679bfd2386e",
"name": "Get Token",
"mode": "link",
"links": [
"be9497eea5625c3f"
],
"x": 455,
"y": 200,
"wires": []
},
{
"id": "b22be99526d4a842",
"type": "link out",
"z": "8c10a679bfd2386e",
"name": "Run query",
"mode": "link",
"links": [
"d9312dbf0a913258"
],
"x": 1005,
"y": 400,
"wires": []
},
{
"id": "d9312dbf0a913258",
"type": "link in",
"z": "8c10a679bfd2386e",
"name": "Run query",
"links": [
"b22be99526d4a842"
],
"x": 75,
"y": 200,
"wires": [
[
"ab8b6368f466dd5a"
]
]
},
{
"id": "4c8b3548cc35074b",
"type": "function",
"z": "8c10a679bfd2386e",
"name": "Handle errors",
"func": "var status\n\nif (msg.payload.graphql[0].extensions) {\n if (msg.payload.graphql[0].extensions.errorCode == \"KT-CT-1124\") {\n // token expired\n var newMsg = {}\n newMsg.refreshToken = true\n newMsg.oldMsg = msg\n status = { fill: \"yellow\", text: \"Obtaining new token\" }\n return [null, newMsg, status]\n }\n else {\n // other octopus error\n node.warn(msg.payload.graphql[0].extensions)\n status = { fill: \"red\", text: msg.payload.graphql[0].extensions.errorCode }\n }\n}\nelse {\n // general graphql error\n node.warn(msg.payload.graphql.error)\n status = { fill: \"red\", text: msg.payload.graphql.error.message}\n}\n\nreturn [msg, null, status];",
"outputs": 3,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 240,
"y": 580,
"wires": [
[],
[
"436150eaf14baa43"
],
[
"b53b2d031b1bac91"
]
],
"outputLabels": [
"GraphQL Error",
"Refresh token",
"Status"
]
},
{
"id": "436150eaf14baa43",
"type": "link out",
"z": "8c10a679bfd2386e",
"name": "Token expired",
"mode": "link",
"links": [
"be9497eea5625c3f"
],
"x": 425,
"y": 640,
"wires": []
},
{
"id": "35bd2d5bcfdc2443",
"type": "link in",
"z": "8c10a679bfd2386e",
"name": "Status",
"links": [
"57e7f83e79d3234d",
"a2a109ed52baf28c",
"b53b2d031b1bac91",
"b5f209b32c478859",
"bad44dd40a123091"
],
"x": 75,
"y": 840,
"wires": [
[
"6c5a11d5aa1aa28e"
]
]
},
{
"id": "178a5d28b4fe2aab",
"type": "link out",
"z": "8c10a679bfd2386e",
"name": "GraphQL error",
"mode": "link",
"links": [
"fefc9c5225e27ee3"
],
"x": 675,
"y": 460,
"wires": []
},
{
"id": "fefc9c5225e27ee3",
"type": "link in",
"z": "8c10a679bfd2386e",
"name": "Handle errors",
"links": [
"07b8cede1bb55008",
"178a5d28b4fe2aab"
],
"x": 75,
"y": 580,
"wires": [
[
"4c8b3548cc35074b"
]
]
},
{
"id": "07b8cede1bb55008",
"type": "link out",
"z": "8c10a679bfd2386e",
"name": "GraphQL error",
"mode": "link",
"links": [
"fefc9c5225e27ee3"
],
"x": 695,
"y": 260,
"wires": []
},
{
"id": "9076d5177c29523e",
"type": "comment",
"z": "8c10a679bfd2386e",
"name": "Run GraphQL query",
"info": "",
"x": 160,
"y": 80,
"wires": []
},
{
"id": "f9d2645c6b0a51be",
"type": "comment",
"z": "8c10a679bfd2386e",
"name": "Obtain Kraken Token",
"info": "",
"x": 160,
"y": 340,
"wires": []
},
{
"id": "7a7f7676f803a447",
"type": "comment",
"z": "8c10a679bfd2386e",
"name": "Error handling",
"info": "",
"x": 140,
"y": 520,
"wires": []
},
{
"id": "b53b2d031b1bac91",
"type": "link out",
"z": "8c10a679bfd2386e",
"name": "Set Status 4",
"mode": "link",
"links": [
"35bd2d5bcfdc2443"
],
"x": 425,
"y": 700,
"wires": []
},
{
"id": "b5f209b32c478859",
"type": "link out",
"z": "8c10a679bfd2386e",
"name": "Set Status 3",
"mode": "link",
"links": [
"35bd2d5bcfdc2443"
],
"x": 1005,
"y": 460,
"wires": []
},
{
"id": "bad44dd40a123091",
"type": "link out",
"z": "8c10a679bfd2386e",
"name": "Set Status 1",
"mode": "link",
"links": [
"35bd2d5bcfdc2443"
],
"x": 455,
"y": 260,
"wires": []
},
{
"id": "beb4a5a15a77d3df",
"type": "function",
"z": "8c10a679bfd2386e",
"name": "Success status",
"func": "var status = { fill: \"green\", text: \"Success\" }\n\nreturn status;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 760,
"y": 200,
"wires": [
[
"a2a109ed52baf28c"
]
]
},
{
"id": "a2a109ed52baf28c",
"type": "link out",
"z": "8c10a679bfd2386e",
"name": "Set Status 2",
"mode": "link",
"links": [
"35bd2d5bcfdc2443"
],
"x": 905,
"y": 200,
"wires": []
},
{
"id": "60e116b69cbdd166",
"type": "inject",
"z": "8c10a679bfd2386e",
"name": "",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "",
"crontab": "",
"once": true,
"onceDelay": 0.1,
"topic": "",
"payload": "",
"payloadType": "date",
"x": 130,
"y": 900,
"wires": [
[
"f28df1509b4cbacc"
]
]
},
{
"id": "f28df1509b4cbacc",
"type": "function",
"z": "8c10a679bfd2386e",
"name": "Initial status",
"func": "\nreturn ({fill: \"blue\", text: \"Ready\"});",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 310,
"y": 900,
"wires": [
[
"57e7f83e79d3234d"
]
]
},
{
"id": "57e7f83e79d3234d",
"type": "link out",
"z": "8c10a679bfd2386e",
"name": "Set Status 5",
"mode": "link",
"links": [
"35bd2d5bcfdc2443"
],
"x": 445,
"y": 900,
"wires": []
},
{
"id": "6c5a11d5aa1aa28e",
"type": "function",
"z": "8c10a679bfd2386e",
"name": "Send status",
"func": "return {payload: msg};",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 310,
"y": 840,
"wires": [
[]
]
},
{
"id": "5046bcad1a0540e3",
"type": "comment",
"z": "8c10a679bfd2386e",
"name": "Status",
"info": "",
"x": 110,
"y": 780,
"wires": []
},
{
"id": "920ed6a5c25f475c",
"type": "graphql-server",
"name": "Octopus API",
"endpoint": "https://api.octopus.energy/v1/graphql/",
"token": ""
},
{
"id": "669992652ac08f13",
"type": "tab",
"label": "Flow 5",
"disabled": false,
"info": "",
"env": []
},
{
"id": "3e4f0f69bffe32c1",
"type": "template",
"z": "669992652ac08f13",
"name": "Get Saving Session info",
"field": "query",
"fieldType": "msg",
"format": "text",
"syntax": "plain",
"template": "query savingSessionInfo($accountNumber: String!) {\n savingSessions(accountNumber: $accountNumber) {\n account(accountNumber: $accountNumber) {\n joinedEvents {\n status\n startAt\n endAt\n eventId\n rewardGivenInOctoPoints\n energySavedInKwh\n baselineConsumptionDeltaKwh\n consumptionDeltaKwh\n netReductionPctRank\n percentageSaved\n }\n }\n events {\n code\n startAt\n endAt\n id\n rewardPerKwhInOctoPoints\n totalParticipants\n }\n }\n}",
"output": "str",
"x": 370,
"y": 100,
"wires": [
[
"542fdd1d8798e46f"
]
]
},
{
"id": "30acccbe7ecc9bf7",
"type": "debug",
"z": "669992652ac08f13",
"name": "GraphQL Error",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 220,
"y": 360,
"wires": []
},
{
"id": "d8ec12ba637d29ea",
"type": "function",
"z": "669992652ac08f13",
"name": "Process data",
"func": "const currentTime = new Date()\n\nvar upcomingJoinedEvents = []\nvar eventsToJoin = []\n\nvar statusMsg = {}\nstatusMsg.is_saving_session = false\n\nfor (let index in msg.payload.graphql.savingSessions.account.joinedEvents) {\n let joinedEvent = msg.payload.graphql.savingSessions.account.joinedEvents[index]\n if (joinedEvent.status == \"UPCOMING\") {\n upcomingJoinedEvents.push(joinedEvent.id)\n\n let start = new Date(joinedEvent.startAt)\n let end = new Date(joinedEvent.endAt)\n \n if (start <= currentTime && currentTime < end) {\n statusMsg.is_saving_session = true\n statusMsg.event = joinedEvent\n }\n }\n}\n\nfor (let index in msg.payload.graphql.savingSessions.events) {\n let event = msg.payload.graphql.savingSessions.events[index]\n let start = new Date(event.startAt)\n \n if (start > currentTime && !upcomingJoinedEvents.includes(event.id)) {\n eventsToJoin.push({ variables: { eventCode: event.code }, event: event})\n }\n}\n\n\nreturn [eventsToJoin, statusMsg];",
"outputs": 2,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 900,
"y": 100,
"wires": [
[
"e37cae2aa44de953"
],
[
"bc77702054e107e7"
]
],
"outputLabels": [
"Events to join",
"Saving Session status"
]
},
{
"id": "936da848bcf0295b",
"type": "template",
"z": "669992652ac08f13",
"name": "Join Saving Session",
"field": "query",
"fieldType": "msg",
"format": "text",
"syntax": "plain",
"template": "mutation MyMutation($accountNumber: String!, $eventCode: String!) {\n joinSavingSessionsEvent(\n input: {accountNumber: $accountNumber, eventCode: $eventCode}\n ) {\n joinedEventCodes\n }\n}",
"output": "str",
"x": 380,
"y": 240,
"wires": [
[
"c1987a7d6434a482"
]
]
},
{
"id": "e37cae2aa44de953",
"type": "link out",
"z": "669992652ac08f13",
"name": "Join Event",
"mode": "link",
"links": [
"df327e523960ebaa"
],
"x": 1075,
"y": 100,
"wires": []
},
{
"id": "df327e523960ebaa",
"type": "link in",
"z": "669992652ac08f13",
"name": "Join event",
"links": [
"e37cae2aa44de953"
],
"x": 75,
"y": 240,
"wires": [
[
"936da848bcf0295b"
]
]
},
{
"id": "2a11c9d0c6ede173",
"type": "cronplus",
"z": "669992652ac08f13",
"name": "Every 30 mins",
"outputField": "payload",
"timeZone": "",
"storeName": "",
"commandResponseMsgOutput": "output1",
"defaultLocation": "",
"defaultLocationType": "default",
"outputs": 1,
"options": [
{
"name": "Schedule",
"topic": "topic1",
"payloadType": "default",
"payload": "",
"expressionType": "cron",
"expression": "*/30 * * * *",
"location": "",
"offset": "0",
"solarType": "all",
"solarEvents": "sunrise,sunset"
}
],
"x": 140,
"y": 100,
"wires": [
[
"3e4f0f69bffe32c1"
]
]
},
{
"id": "5bccc3537aa819d3",
"type": "function",
"z": "669992652ac08f13",
"name": "Prepare notification",
"func": "let start = new Date(msg.event.startAt)\nlet end = new Date(msg.event.endAt)\n\nvar newMsg = {}\nnewMsg.topic = \"Joined Octopus Saving Session\"\nnewMsg.sound = \"cashregister\"\nnewMsg.payload = start.toDateString() + \" \" + start.toLocaleTimeString() + \" - \" + end.toLocaleTimeString() + \" \\n\" + msg.event.rewardPerKwhInOctoPoints + \" points per Kwh\"\nreturn newMsg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 920,
"y": 240,
"wires": [
[
"37827af75dcea516"
]
]
},
{
"id": "37827af75dcea516",
"type": "pushover",
"z": "669992652ac08f13",
"name": "",
"device": "",
"title": "",
"priority": 0,
"sound": "",
"url": "",
"url_title": "",
"html": false,
"x": 1140,
"y": 240,
"wires": []
},
{
"id": "542fdd1d8798e46f",
"type": "subflow:8c10a679bfd2386e",
"z": "669992652ac08f13",
"name": "",
"x": 640,
"y": 100,
"wires": [
[
"d8ec12ba637d29ea"
],
[
"5e4bcdf438802a2c"
]
]
},
{
"id": "c1987a7d6434a482",
"type": "subflow:8c10a679bfd2386e",
"z": "669992652ac08f13",
"name": "",
"x": 640,
"y": 240,
"wires": [
[
"5bccc3537aa819d3"
],
[
"668a0e1ddb696859"
]
]
},
{
"id": "bc77702054e107e7",
"type": "link out",
"z": "669992652ac08f13",
"name": "Saving Session status",
"mode": "link",
"links": [
"baf377b3fc2d4ccb"
],
"x": 1075,
"y": 140,
"wires": []
},
{
"id": "baf377b3fc2d4ccb",
"type": "link in",
"z": "669992652ac08f13",
"name": "Saving Session status",
"links": [
"bc77702054e107e7"
],
"x": 75,
"y": 480,
"wires": [
[
"74252578b1c97c0a"
]
]
},
{
"id": "75001674c4d10204",
"type": "switch",
"z": "669992652ac08f13",
"name": "Saving Session (Start/End)",
"property": "is_saving_session",
"propertyType": "msg",
"rules": [
{
"t": "true"
},
{
"t": "false"
}
],
"checkall": "true",
"repair": false,
"outputs": 2,
"x": 480,
"y": 480,
"wires": [
[],
[]
],
"outputLabels": [
"Saving Session Started",
"Saving Session Ended"
]
},
{
"id": "74252578b1c97c0a",
"type": "rbe",
"z": "669992652ac08f13",
"name": "State change",
"func": "rbe",
"gap": "",
"start": "",
"inout": "out",
"septopics": false,
"property": "is_saving_session",
"topi": "topic",
"x": 220,
"y": 480,
"wires": [
[
"75001674c4d10204"
]
]
},
{
"id": "5ae0a70ae8bd4599",
"type": "comment",
"z": "669992652ac08f13",
"name": "Saving Session Start/End",
"info": "",
"x": 170,
"y": 440,
"wires": []
},
{
"id": "5e4bcdf438802a2c",
"type": "link out",
"z": "669992652ac08f13",
"name": "GraphQL error 1",
"mode": "link",
"links": [
"2c96e06a442e8e73"
],
"x": 835,
"y": 140,
"wires": []
},
{
"id": "668a0e1ddb696859",
"type": "link out",
"z": "669992652ac08f13",
"name": "GraphQL error 2",
"mode": "link",
"links": [
"2c96e06a442e8e73"
],
"x": 835,
"y": 280,
"wires": []
},
{
"id": "2c96e06a442e8e73",
"type": "link in",
"z": "669992652ac08f13",
"name": "GraphQL error",
"links": [
"5e4bcdf438802a2c",
"668a0e1ddb696859"
],
"x": 75,
"y": 360,
"wires": [
[
"30acccbe7ecc9bf7"
]
]
},
{
"id": "d475f13acacc35c9",
"type": "comment",
"z": "669992652ac08f13",
"name": "GraphQL error debug",
"info": "",
"x": 160,
"y": 320,
"wires": []
},
{
"id": "f91f724766790b34",
"type": "comment",
"z": "669992652ac08f13",
"name": "Join Saving Session",
"info": "",
"x": 160,
"y": 200,
"wires": []
},
{
"id": "7b2b6d5fb83db01c",
"type": "comment",
"z": "669992652ac08f13",
"name": "Check for Saving Sessions",
"info": "",
"x": 180,
"y": 60,
"wires": []
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment