Last active
March 20, 2025 08:16
-
-
Save andrewreid/0bc32e3574ab7c53c3d35cb4419f5fe8 to your computer and use it in GitHub Desktop.
Node-RED flow for importing Amber Electric wholesale electricity prices into VRM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"id": "ede00cd79443d77a", | |
"type": "vrm-api", | |
"z": "405cbd599f05d654", | |
"vrm": "9fb7325ee595c4a0", | |
"name": "Change Dynamic ESS settings", | |
"api_type": "installations", | |
"idUser": "", | |
"users": "", | |
"idSite": "510670", | |
"installations": "patch-dynamic-ess-settings", | |
"attribute": "", | |
"stats_interval": "", | |
"show_instance": false, | |
"stats_start": "", | |
"stats_end": "", | |
"use_utc": false, | |
"gps_start": "", | |
"gps_end": "", | |
"widgets": "", | |
"instance": "", | |
"vrm_id": "", | |
"country": "", | |
"b_max": "", | |
"tb_max": "", | |
"fb_max": "", | |
"tg_max": "", | |
"fg_max": "", | |
"b_cycle_cost": "", | |
"buy_price_formula": "", | |
"sell_price_formula": "", | |
"green_mode_on": "", | |
"feed_in_possible": "", | |
"feed_in_control_on": "", | |
"b_goal_hour": "", | |
"b_goal_SOC": "", | |
"store_in_global_context": false, | |
"verbose": false, | |
"x": 890, | |
"y": 400, | |
"wires": [ | |
[ | |
"18ffab3c077f6ef2" | |
] | |
] | |
}, | |
{ | |
"id": "50fd4622ae311d59", | |
"type": "function", | |
"z": "405cbd599f05d654", | |
"name": "Set Dynamic ESS prices", | |
"func": "// Extract the intervals from the payload\nconst intervals = msg.payload;\n\n// Create empty arrays to hold buy and sell schedules\nlet buySchedule = [];\nlet sellSchedule = [];\n\n// Helper function to convert UTC time to local time\nconst toLocalTime = (isoTime) => {\n const date = new Date(isoTime);\n const options = {\n hour: '2-digit',\n minute: '2-digit',\n hour12: false,\n timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone // Local timezone\n };\n let time = new Intl.DateTimeFormat('en-US', options).format(date);\n\n // Fix for midnight time formatting\n if (time.startsWith(\"24:\")) {\n time = time.replace(\"24:\", \"00:\");\n }\n\n return time;\n};\n\n// Iterate through each interval and sort data into buy and sell schedules\nintervals.forEach(interval => {\n const from = toLocalTime(interval.startTime); // Convert to local time\n const to = toLocalTime(interval.endTime); // Convert to local time\n\n // Extract and format price\n let price = parseFloat(interval.perKwh);\n\n if (interval.channelType === \"general\") {\n // Convert buy price to dollars and round to the nearest cent\n price = Math.round(price) / 100;\n buySchedule.push({ from: from, to: to, price: price });\n } else if (interval.channelType === \"feedIn\") {\n // Invert sell price, convert to dollars, and round to the nearest cent\n price = Math.round(-price) / 100;\n sellSchedule.push({ from: from, to: to, price: price });\n }\n});\n\n// Create the final output structure\nconst buyPriceSchedule = [{\n days: [0, 1, 2, 3, 4, 5, 6], // All days of the week\n schedule: buySchedule\n}];\n\nconst sellPriceSchedule = [{\n days: [0, 1, 2, 3, 4, 5, 6], // All days of the week\n schedule: sellSchedule\n}];\n\n// Assign the formatted schedules to the payload\nmsg.payload = {\n buyPriceSchedule: JSON.stringify(buyPriceSchedule),\n sellPriceSchedule: JSON.stringify(sellPriceSchedule)\n};\n\nreturn msg;\n", | |
"outputs": 1, | |
"timeout": 0, | |
"noerr": 0, | |
"initialize": "", | |
"finalize": "", | |
"libs": [], | |
"x": 610, | |
"y": 340, | |
"wires": [ | |
[ | |
"d1263bc8dc5a4fa3", | |
"ede00cd79443d77a" | |
] | |
] | |
}, | |
{ | |
"id": "18ffab3c077f6ef2", | |
"type": "debug", | |
"z": "405cbd599f05d654", | |
"name": "debug 1", | |
"active": true, | |
"tosidebar": true, | |
"console": false, | |
"tostatus": false, | |
"complete": "false", | |
"statusVal": "", | |
"statusType": "auto", | |
"x": 940, | |
"y": 460, | |
"wires": [] | |
}, | |
{ | |
"id": "d1263bc8dc5a4fa3", | |
"type": "debug", | |
"z": "405cbd599f05d654", | |
"name": "DESS prices debug", | |
"active": true, | |
"tosidebar": true, | |
"console": false, | |
"tostatus": false, | |
"complete": "true", | |
"targetType": "full", | |
"statusVal": "", | |
"statusType": "auto", | |
"x": 850, | |
"y": 340, | |
"wires": [] | |
}, | |
{ | |
"id": "04e22901d3bfa99b", | |
"type": "http request", | |
"z": "405cbd599f05d654", | |
"name": "Get forecast prices", | |
"method": "GET", | |
"ret": "obj", | |
"paytoqs": "ignore", | |
"url": "https://api.amber.com.au/v1/sites/SITE_ID/prices", | |
"tls": "", | |
"persist": false, | |
"proxy": "", | |
"insecureHTTPParser": false, | |
"authType": "bearer", | |
"senderr": false, | |
"headers": [], | |
"x": 310, | |
"y": 340, | |
"wires": [ | |
[ | |
"50fd4622ae311d59" | |
] | |
] | |
}, | |
{ | |
"id": "c014fe04f830ada0", | |
"type": "inject", | |
"z": "405cbd599f05d654", | |
"name": "", | |
"props": [ | |
{ | |
"p": "payload" | |
} | |
], | |
"repeat": "300", | |
"crontab": "", | |
"once": true, | |
"onceDelay": 0.1, | |
"topic": "", | |
"payload": "", | |
"payloadType": "str", | |
"x": 110, | |
"y": 340, | |
"wires": [ | |
[ | |
"04e22901d3bfa99b" | |
] | |
] | |
}, | |
{ | |
"id": "9fb7325ee595c4a0", | |
"type": "config-vrm-api", | |
"name": "VRM-Wattle Park" | |
} | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment