Best strategy for Reading & Sorting Electricity Cost Array

A little example how hourly (or half hour) price information can be turned into daily on/off schedule.
NB! This is not ready to use solution, just a little something to start from. Or start to generate ideas from...

[{"id":"cf605fd3.ce51","type":"function","z":"eab6da64.c00bf8","name":"generate some random price data","func":"var prices = []\n// 48 elements in exampe\n// 24 sould do if price change is hourly based\nfor(let i=0;i<48;i++){\n    var p = parseFloat((5 + Math.random()*5).toFixed(2))\n    prices.push(p)\n}\nglobal.set(\"prices\",prices)","outputs":1,"noerr":0,"x":390,"y":80,"wires":[[]]},{"id":"d8b5c6b7.5552e8","type":"inject","z":"eab6da64.c00bf8","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":80,"wires":[["cf605fd3.ce51"]]},{"id":"17fd23ea.02640c","type":"inject","z":"eab6da64.c00bf8","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":130,"y":170,"wires":[["88992323.8235"]]},{"id":"88992323.8235","type":"function","z":"eab6da64.c00bf8","name":"create schedule from price data","func":"let p = global.get(\"prices\")// read raw prices\nlet schedule = []// create new array\nfunction moveToSchedule(v,i,a){\n    v = v+(i*0.0000000001)// change price values a tiny bit to get rid of duplicates\n    schedule.push({state:true,price:v})// create price objects with state (all states true)\n}\np.forEach(moveToSchedule)// run creation element by element\nglobal.set(\"schedule\",schedule)// store schedule to global\n","outputs":1,"noerr":0,"x":380,"y":170,"wires":[[]]},{"id":"4cb9f496.e814cc","type":"inject","z":"eab6da64.c00bf8","name":"","topic":"","payload":"6","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":250,"wires":[["57326dcc.af1914"]]},{"id":"57326dcc.af1914","type":"function","z":"eab6da64.c00bf8","name":"find and change number of elements in schedule","func":"let schedule = global.get(\"schedule\")//get current schedule array\n//copy original schedule cos we need sorted version of it\nlet copy = schedule.concat()\n//slice a count of elements from sorted array (count comes from msg.payload)\n//sliced part is from high end of array sorted by price\n// can be changed by sorting rule a.price - b.price\nlet selection = copy.sort((a,b) => b.price - a.price).slice(0,msg.payload);\n//run state change against each of original schedule element\nschedule.forEach(changeState);\n\nfunction changeState(e){\n    //find index of element matching the element in sliced part\n    let idx = selection.findIndex(el => el.price === e.price)\n    //change element state. if index is -1, it is not part of selection\n    //swapping true and false creates oposite result for schedule\n    e.state = idx === -1 ? true : false\n}\n//store schedule\nglobal.set(\"schedule\",schedule)\n\n// if needed, send out message\n//msg.topic = \"schedule updated\"\n//msg.payload = \"something related\"\n//return msg","outputs":1,"noerr":0,"x":430,"y":250,"wires":[[]]},{"id":"79830bde.295314","type":"comment","z":"eab6da64.c00bf8","name":"1. create some fake price data","info":"48 elements ","x":200,"y":40,"wires":[]},{"id":"d087eed5.f1b79","type":"comment","z":"eab6da64.c00bf8","name":"2. generate schedule based on price data","info":"each element in schedule is price combined with state for that period\nschedule needed to be create only once\nlater on the schedule can be updated after new price data is requested\nNB! this example does not contain part of price update in existing schedule","x":250,"y":130,"wires":[]},{"id":"6817e54e.bc69bc","type":"comment","z":"eab6da64.c00bf8","name":"3. Change schedule based on prices","info":"","x":220,"y":210,"wires":[]}]
2 Likes

Thanks @hotNipi- it'll take me a bit of time to work through this- not written code for many years.

I now have the Tesla and Modbus charger interfaces so moving closer- not bad for a week with Node Red!

A small thing I meant to point out for others is you don't need API key for this application- only if you want to check your own consumption, etc.

Been following this closely and built on it to suit my own particular needs, a couple of things to sort out, but BIG BIG thanks to you all for contributing. It has made my job a lot easier and I have learnt loads, even got the costs per kWh for each combined slot. (Always loved hated Array manipulation!)

Anyway, last night, there was no fresh data available from Octopus until sometime after 20:00hrs, all I got was the previous days data.

This morning I used a catch node to check if there was a value in the InfluxDB for tomorrow at 01:00hrs. If the slot is empty (check after I have the data stored locally), I have created and msg.error that is then caught by a Catch node to start another fetch, this then runs very 15 minutes (from Inject Node) until midnight (figure if it hasn't picked anything up by then, something drastic is wrong!).

Would have liked something like 23 minutes (Inject node only does 15 minute slots). Why 23 minutes? Could be almost anything, as this means that we move our fetch away from a 'Main' time when everybody else will do a fetch (hour/half hour/quarters), hopefully, you get to the server when there is less load. But figure that the fetch after handling at this end will move me away slightly further each time.

You might want to try the cron-plus node for more flexibility.

1 Like

Thanks Julian, I will take a look. Still expanding my knowledge!!

@mudwalker, is there code you can share? I'm very new to Node-Red (and coding) so any examples really help understanding.
I have Influxdb installed for logging local sensors and would like to add the Octopus data but still to get to that part of learning.
Dates/ time are very hard- never mind in complex arrays! This week I plan to look at CronPlus, BigTimer and Google Calendar.
Last week was spent getting Modbus working reliably to the EV charger and power meters.
I already have a lot of Zwave, WiFi and Zigbee devices being constrained at peak times as well as changing to electric heating from gas at times I get paid to use electricity.

I thought I'd share the net effect- bear in mind that this means not just a noteable saving but also a significant carbon reduction! This becomes much more important to the whole grid when we are allowed out and driving again!

I can share, but I need some time to clean things up, it really is development code from an amateur.

Don't expect anything flashy, probably broken every rule in the book in writing the code, but would be grateful for a Peer review! Give me a couple of days.

As a taster, I have attached a screenshot, complete with hotNipi's Artless Gauges. These only have dummy figures at the moment, just waiting for some Wemos D1 minis from China, but already have a PZEM004T to connect to it, I will be using that with Tasmota Sensor.

Screenshot from 2020-04-28 16-31-52|656x500

Ooops, can see the crash when we get close to the end of the data in the 3 hour slot!

1 Like

Haha, we are all doing that :slight_smile:

OK, not as bad as I thought!! Had to post a file, hope this works.

Be gentle! (hotNipi's meters not included - different flow)
Octopus.json (38.4 KB)

Edit, FYI - eMail at the bottom is to send the 'Best Prices' every morning (Used by a relation!).

Thanks @mudwalker
Have a look tomorrow- wore myself out on something else today!

Finally managed to get a workable smart TOU Tesla charger working!

A little time working on other things and it became obvious that I don't need to worry about manipulating best slot times. I now just pick the highest price of the best slots, check this against the current price and then then start/ stop charging as appropriate.
There is also an extra step to increase the charge limit if price is below zero to help the grid.

The start time for the best slots are set when the car is connected to the charger, the stop time I'll set from calendar or possibly an Alexa reminder (not sure if I can do that!).

I still need to add checks if car is already charging, etc and use data from Influx rather than online, but here is an example with some Tesla API data.

Thanks for all the help and ideas- I may reach second grade of my very belated programming induction soon! :wink:

All I need now is to be allowed out of the house to drive it so it actually needs charged...

[{"id":"1a0bf71.4a80e09","type":"inject","z":"ea6c92fd.6c133","name":"","topic":"","payload":"12","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":470,"y":500,"wires":[["25ebf3ec.e074ec"]]},{"id":"7a5c5946.728408","type":"json","z":"ea6c92fd.6c133","name":"","property":"payload","action":"obj","pretty":false,"x":1010,"y":500,"wires":[["4d656dcc.6e7694"]]},{"id":"d2e48ee3.92145","type":"split","z":"ea6c92fd.6c133","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":1270,"y":500,"wires":[["6d026de5.9a7fc4"]]},{"id":"4d656dcc.6e7694","type":"change","z":"ea6c92fd.6c133","name":"Results","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.results","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1140,"y":500,"wires":[["d2e48ee3.92145"]]},{"id":"6d026de5.9a7fc4","type":"link out","z":"ea6c92fd.6c133","name":"","links":["8e60f88c.14de78","19da7595.edab1a"],"x":1395,"y":500,"wires":[]},{"id":"be5bb33c.baeac","type":"batch","z":"ea6c92fd.6c133","name":"Batch 1 period","mode":"count","count":"1","overlap":"0","interval":10,"allowEmptySequence":false,"topics":[],"x":480,"y":600,"wires":[["4bf161d9.95f8"]]},{"id":"19da7595.edab1a","type":"link in","z":"ea6c92fd.6c133","name":"","links":["6d026de5.9a7fc4"],"x":315,"y":600,"wires":[["be5bb33c.baeac"]]},{"id":"4bf161d9.95f8","type":"join","z":"ea6c92fd.6c133","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":650,"y":600,"wires":[["867ef5a9.2512c8"]]},{"id":"867ef5a9.2512c8","type":"function","z":"ea6c92fd.6c133","name":"Cost for batch","func":"function addValue(arr) {\n    let add = 0;\n    for (let ele of arr) {\n        add = add + ele.value_inc_vat;\n    }\n    return add;\n}\n\nmsg.value_batch = addValue(msg.payload);\n\nreturn msg;","outputs":1,"noerr":0,"x":820,"y":600,"wires":[["d63332c6.bc9d1","fffc4985.4061b8"]],"info":"Calculate the energy cost for the period"},{"id":"d63332c6.bc9d1","type":"change","z":"ea6c92fd.6c133","name":"Transform","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload[0].valid_from","tot":"msg"},{"t":"set","p":"pay.start","pt":"msg","to":"payload[0].valid_from","tot":"msg"},{"t":"set","p":"pay.time_end","pt":"msg","to":"payload[0].valid_to","tot":"msg"},{"t":"move","p":"value_batch","pt":"msg","to":"pay.value","tot":"msg"},{"t":"delete","p":"filename","pt":"msg"},{"t":"delete","p":"_event","pt":"msg"},{"t":"set","p":"payload","pt":"msg","to":"pay.value","tot":"msg"},{"t":"delete","p":"ChargeStart","pt":"msg"},{"t":"delete","p":"ChargeStop","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":500,"y":660,"wires":[["1d1019f1.3248a6"]]},{"id":"1d1019f1.3248a6","type":"join","z":"ea6c92fd.6c133","name":"Create Array","mode":"custom","build":"array","property":"pay","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"2","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":650,"y":660,"wires":[["b99b7592.b95cc8"]]},{"id":"fffc4985.4061b8","type":"debug","z":"ea6c92fd.6c133","name":"CfB","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1010,"y":600,"wires":[]},{"id":"2c14e3e8.1d33bc","type":"debug","z":"ea6c92fd.6c133","name":"CA","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1210,"y":720,"wires":[]},{"id":"25ebf3ec.e074ec","type":"change","z":"ea6c92fd.6c133","name":"","rules":[{"t":"set","p":"slots","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":635,"y":500,"wires":[["5cc4f44f.acad7c"]]},{"id":"5cc4f44f.acad7c","type":"http request","z":"ea6c92fd.6c133","name":"","method":"GET","ret":"txt","paytoqs":false,"url":"https://api.octopus.energy/v1/products/AGILE-18-02-21/electricity-tariffs/E-1R-AGILE-18-02-21-N/standard-unit-rates/?period_from={{{ChargeStart}}}&period_to={{{ChargeStop}}}","tls":"","persist":false,"proxy":"","authType":"","x":810,"y":500,"wires":[["7a5c5946.728408","49faf7a.2c0f208"]]},{"id":"e25c00ef.7e539","type":"debug","z":"ea6c92fd.6c133","name":"BS","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1000,"y":720,"wires":[]},{"id":"49faf7a.2c0f208","type":"debug","z":"ea6c92fd.6c133","name":"url","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":960,"y":440,"wires":[]},{"id":"a1e9bbda.c422c8","type":"function","z":"ea6c92fd.6c133","name":"Charge Price Limit","func":"const cycle = 1;\n\nmsg.outcome = msg.lowest.sort((a,b) => b - a);\n\nfor (let c = 0; c < cycle ; c++) {\n    global.set ('ChargePriceLimit', msg.outcome[c]);\n    node.send({\"topic\" : \" ChargePriceLimit\", \"payload.ChargePriceLimit\" : msg.outcome[c]});\n}\n","outputs":1,"noerr":0,"x":1210,"y":660,"wires":[["5a67a464.10dedc"]]},{"id":"b99b7592.b95cc8","type":"function","z":"ea6c92fd.6c133","name":"Best Slots","func":"if (msg.slots > msg.pay.length) {\n    msg.slots = msg.pay.length;\n}\n\nconst cycle = msg.slots;\n\nmsg.outcome = msg.pay.sort((a,b) => a.value - b.value);\n\nfor (let c = 0; c < cycle ; c++) {\n    node.send({\"topic\" : \"Lowest-\"+c, \"lowest\" : msg.outcome[c].value});\n}\n","outputs":1,"noerr":0,"x":840,"y":660,"wires":[["e25c00ef.7e539","e6e7ce4a.49d23"]]},{"id":"e6e7ce4a.49d23","type":"join","z":"ea6c92fd.6c133","name":"Create Array","mode":"custom","build":"array","property":"lowest","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"2","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":1010,"y":660,"wires":[["a1e9bbda.c422c8","2c14e3e8.1d33bc"]]},{"id":"8a0d71aa.09c56","type":"inject","z":"ea6c92fd.6c133","name":"Tesla vehicleData Query","topic":"","payload":"{\"id\":1234567890987654,\"user_id\":654321,\"vehicle_id\":123456789,\"vin\":\"abcdefgefhijklmno\",\"display_name\":\"Tesla\",\"option_codes\":\"AD15,MDL3,PBSB,RENA,BT37,ID3W,RF3G,S3PB,DRLH,DV2W,W39B,APF0,COUS,BC3B,CH07,PC30,FC3P,FG31,GLFR,HL31,HM31,IL31,LTPB,MR31,FM3B,RS3H,SA3P,STCP,SC04,SU3C,T3CA,TW00,TM00,UT3P,WR00,AU3P,APH3,AF00,ZCST,MI00,CDM0\",\"color\":null,\"tokens\":[\"33b1234567890\",\"1234567890b3\"],\"state\":\"online\",\"in_service\":false,\"id_s\":\"1111111111111111\",\"calendar_enabled\":true,\"api_version\":7,\"backseat_token\":null,\"backseat_token_updated_at\":null,\"charge_state\":{\"battery_heater_on\":false,\"battery_level\":40,\"battery_range\":184.72,\"charge_current_request\":6,\"charge_current_request_max\":6,\"charge_enable_request\":false,\"charge_energy_added\":0,\"charge_limit_soc\":90,\"charge_limit_soc_max\":100,\"charge_limit_soc_min\":50,\"charge_limit_soc_std\":90,\"charge_miles_added_ideal\":0,\"charge_miles_added_rated\":0,\"charge_port_cold_weather_mode\":null,\"charge_port_door_open\":true,\"charge_port_latch\":\"Engaged\",\"charge_rate\":0,\"charge_to_max_range\":false,\"charger_actual_current\":0,\"charger_phases\":null,\"charger_pilot_current\":6,\"charger_power\":0,\"charger_voltage\":0,\"charging_state\":\"Stopped\",\"conn_charge_cable\":\"IEC\",\"est_battery_range\":142.6,\"fast_charger_brand\":\"<invalid>\",\"fast_charger_present\":false,\"fast_charger_type\":\"<invalid>\",\"ideal_battery_range\":147.77,\"managed_charging_active\":false,\"managed_charging_start_time\":null,\"managed_charging_user_canceled\":false,\"max_range_charge_counter\":0,\"minutes_to_full_charge\":1230,\"not_enough_power_to_heat\":false,\"scheduled_charging_pending\":false,\"scheduled_charging_start_time\":null,\"time_to_full_charge\":20.5,\"timestamp\":1588265747583,\"trip_charging\":false,\"usable_battery_level\":61,\"user_charge_enable_request\":false},\"climate_state\":{\"battery_heater\":false,\"battery_heater_no_power\":false,\"climate_keeper_mode\":\"off\",\"defrost_mode\":0,\"driver_temp_setting\":21,\"fan_status\":0,\"inside_temp\":null,\"is_auto_conditioning_on\":null,\"is_climate_on\":false,\"is_front_defroster_on\":false,\"is_preconditioning\":false,\"is_rear_defroster_on\":false,\"left_temp_direction\":null,\"max_avail_temp\":28,\"min_avail_temp\":15,\"outside_temp\":20,\"passenger_temp_setting\":21,\"remote_heater_control_enabled\":false,\"right_temp_direction\":null,\"seat_heater_left\":0,\"seat_heater_rear_center\":0,\"seat_heater_rear_left\":0,\"seat_heater_rear_right\":0,\"seat_heater_right\":0,\"side_mirror_heaters\":false,\"steering_wheel_heater\":false,\"timestamp\":1588265747583,\"wiper_blade_heater\":false},\"drive_state\":{\"gps_as_of\":1588265746,\"heading\":315,\"latitude\":35.127169,\"longitude\":-7.123456,\"native_latitude\":57.581949,\"native_location_supported\":1,\"native_longitude\":-3.123564,\"native_type\":\"wgs\",\"power\":0,\"shift_state\":null,\"speed\":null,\"timestamp\":1588265747583},\"gui_settings\":{\"gui_24_hour_time\":true,\"gui_charge_rate_units\":\"mi/hr\",\"gui_distance_units\":\"mi/hr\",\"gui_range_display\":\"Ideal\",\"gui_temperature_units\":\"C\",\"show_range_units\":true,\"timestamp\":1588265747583},\"vehicle_config\":{\"can_accept_navigation_requests\":true,\"can_actuate_trunks\":true,\"car_special_type\":\"base\",\"car_type\":\"models2\",\"charge_port_type\":\"EU\",\"ece_restrictions\":true,\"eu_vehicle\":true,\"exterior_color\":\"Red\",\"has_air_suspension\":true,\"has_ludicrous_mode\":true,\"motorized_charge_port\":true,\"plg\":true,\"rear_seat_heaters\":1,\"rear_seat_type\":1,\"rhd\":true,\"roof_color\":\"None\",\"seat_type\":1,\"spoiler_type\":\"Passive\",\"sun_roof_installed\":2,\"third_row_seats\":\"None\",\"timestamp\":1588265747584,\"trim_badging\":\"p90d\",\"use_range_badging\":false,\"wheel_type\":\"Base19\"},\"vehicle_state\":{\"api_version\":7,\"autopark_state_v2\":\"standby\",\"autopark_style\":\"dead_man\",\"calendar_supported\":true,\"car_version\":\"2020.12.5 e1144a6600ff\",\"center_display_state\":0,\"df\":0,\"dr\":0,\"ft\":0,\"homelink_device_count\":1,\"homelink_nearby\":true,\"is_user_present\":false,\"last_autopark_error\":\"no_error\",\"locked\":true,\"media_state\":{\"remote_control_enabled\":true},\"notifications_supported\":true,\"odometer\":18660.410966,\"parsed_calendar_supported\":true,\"pf\":0,\"pr\":0,\"remote_start\":false,\"remote_start_enabled\":true,\"remote_start_supported\":true,\"rt\":0,\"smart_summon_available\":false,\"software_update\":{\"download_perc\":0,\"expected_duration_sec\":2700,\"install_perc\":1,\"status\":\"\",\"version\":\"\"},\"speed_limit_mode\":{\"active\":false,\"current_limit_mph\":50,\"max_limit_mph\":90,\"min_limit_mph\":50,\"pin_code_set\":false},\"summon_standby_mode_enabled\":false,\"sun_roof_percent_open\":0,\"sun_roof_state\":\"unknown\",\"timestamp\":1588265747583,\"valet_mode\":false,\"valet_pin_needed\":true,\"vehicle_name\":\"Bob\"}}","payloadType":"json","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":390,"y":320,"wires":[["c5868517.b9fa78","ff471269.74a07"]]},{"id":"c5868517.b9fa78","type":"debug","z":"ea6c92fd.6c133","name":"TezData","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":740,"y":320,"wires":[]},{"id":"ff471269.74a07","type":"function","z":"ea6c92fd.6c133","name":"CalcCharge","func":"//Get the Battery Charging Limit % and Current State of Charge % from API and multiply by Battery Capacity (last number)\nvar ChargekWh = (msg.payload.charge_state.charge_limit_soc - msg.payload.charge_state.battery_level)/100 * 90 \n\n//Work out how many charge slots required based on charger capacity (half 7kWh here). Add a multiplier for estimated inefficiency (1.1 here)\nmsg.slots = parseFloat((ChargekWh/3.5)*1.1).toFixed(0)\nmsg.slots = Number(msg.slots)\n\n//Set Charge Start to Now\nmsg.ChargeStart = new Date().toISOString()\n\n//Set Charge Stop to 7am next day\nmsg.ChargeStop = \"2020-05-07T07:00:00.000Z\"\nnode.status({fill:\"yellow\", shape:\"ring\", text:\"Charge Req'd \" + parseFloat(ChargekWh).toFixed(0) + \"kWh, Charge Slots \" + msg.slots})\nreturn msg;","outputs":1,"noerr":0,"x":390,"y":440,"wires":[["5cc4f44f.acad7c"]]},{"id":"a6f373d2.6af52","type":"comment","z":"ea6c92fd.6c133","name":"Smart TOU Charge Timer For Octopus Agile","info":"","x":670,"y":140,"wires":[]},{"id":"b916e6ca.b929e8","type":"http request","z":"ea6c92fd.6c133","name":"","method":"GET","ret":"txt","paytoqs":false,"url":"https://api.octopus.energy/v1/products/AGILE-18-02-21/electricity-tariffs/E-1R-AGILE-18-02-21-N/standard-unit-rates/?period_from={{{Now}}}&period_to={{{Next}}}","tls":"","persist":false,"proxy":"","authType":"","x":870,"y":860,"wires":[["6ae9f94a.e997e8"]]},{"id":"43892c79.26b1e4","type":"function","z":"ea6c92fd.6c133","name":"TimeNow","func":"//Get Current Octopus Agile Rate\n\nmsg.Now = new Date().toISOString()\n\n//Set Charge Stop to 7am next day\n//msg.Next = new Date().toISOString()\nnode.status({fill:\"yellow\", shape:\"ring\", text:msg.Now})\nreturn msg;","outputs":1,"noerr":0,"x":440,"y":860,"wires":[["ebe20ad5.0b4748"]]},{"id":"2e4186de.cea5ba","type":"debug","z":"ea6c92fd.6c133","name":"PriceNow","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":1861,"y":860,"wires":[]},{"id":"ebe20ad5.0b4748","type":"moment","z":"ea6c92fd.6c133","name":"","topic":"","input":"Next","inputType":"msg","inTz":"Africa/Abidjan","adjAmount":"3","adjType":"seconds","adjDir":"add","format":"","locale":"en_GB","output":"Next","outputType":"msg","outTz":"Africa/Abidjan","x":664,"y":860,"wires":[["b916e6ca.b929e8"]]},{"id":"76a3da8e.c34994","type":"change","z":"ea6c92fd.6c133","name":"Transform","rules":[{"t":"delete","p":"Now","pt":"msg"},{"t":"delete","p":"Next","pt":"msg"},{"t":"set","p":"ElecPriceNow","pt":"msg","to":"payload.results[0].value_inc_vat","tot":"msg"},{"t":"set","p":"ElecPriceNow","pt":"global","to":"ElecPriceNow","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1160,"y":860,"wires":[["5a67a464.10dedc"]],"info":"node.status({fill:\"yellow\", shape:\"ring\", text:\"Charge \" + msg.ElecPriceNow})"},{"id":"6ae9f94a.e997e8","type":"json","z":"ea6c92fd.6c133","name":"","property":"payload","action":"obj","pretty":false,"x":1020,"y":860,"wires":[["76a3da8e.c34994"]]},{"id":"5a67a464.10dedc","type":"function","z":"ea6c92fd.6c133","name":"TOU Charge","func":"var ChargePriceLimit = global.get ('ChargePriceLimit')\nvar ElecPriceNow = global.get ('ElecPriceNow')\n\nif (ElecPriceNow > ChargePriceLimit) {\n    node.status({fill:\"red\", shape:\"ring\", text:\"Stop Charge= \" + ElecPriceNow +  \"/L \" + ChargePriceLimit + \"p\"})\n    msg.command = \"stopCharge\"\n    return msg;\n}\nif (ElecPriceNow <= ChargePriceLimit) {\n    node.status({fill:\"green\", shape:\"ring\", text:\"Start Charge=\" + ElecPriceNow + \"/L \" + ChargePriceLimit + \"p\"})\n    msg.command = \"startCharge\"\n    return msg;\n}","outputs":1,"noerr":0,"x":1331,"y":860,"wires":[["960ce091.6094d","2e4186de.cea5ba"]]},{"id":"100a1f24.36f311","type":"cronplus","z":"ea6c92fd.6c133","name":"GetPriceNow","outputField":"payload","timeZone":"","options":[{"topic":"TOU Half Hourly","payload":"","type":"date","expression":"0 0,30 * * * *"}],"x":230,"y":860,"wires":[["43892c79.26b1e4"]]},{"id":"960ce091.6094d","type":"function","z":"ea6c92fd.6c133","name":"Rate Below Zero","func":"var ElecPriceNow = global.get ('ElecPriceNow')\n\nif (ElecPriceNow <= 0) {\n    node.status({fill:\"blue\", shape:\"ring\", text:\"Charging to 90%\"})\n    msg.command = \"setChargeLimit\"\n    msg.commandArgs = \"90\"\n    return msg;\n}","outputs":1,"noerr":0,"x":1530,"y":820,"wires":[["2b942b24.de8e14"]]},{"id":"1b64a017.dbe62","type":"inject","z":"ea6c92fd.6c133","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":230,"y":800,"wires":[["43892c79.26b1e4"]]},{"id":"2b942b24.de8e14","type":"delay","z":"ea6c92fd.6c133","name":"","pauseType":"delay","timeout":"3","timeoutUnits":"minutes","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":1700,"y":820,"wires":[["2e4186de.cea5ba"]]}]
1 Like

Looks great. Can you share your node red flow?

No. I don't have this flow long ago. And even then, sharing it would not do any good cos it was not created to be shared in mind. It was tightly bounded to my physical setup so whole flow is just a non-working mess for any other user but me.

If you are interested about those graphical widgets - they are done with ui_template (again, works well only with exact site settings so again not directly shareable but may be easier to fix.) But as I don't use them anymore, I don't have just to share. (I do throw away garbage)

If you are interested about technical part of implementation, I doubt we are at same technical setup and requirements. Every solution is somehow unique. Start building one and ask for help with dedicated questions - how to do this or that. The community here is very helpful so it takes just to be started :slight_smile:

@Andrei I have come a little late to the game and have now integrated your Agile Octopus flows for both the ascending sorted periods and the lowest cost X contiguous periods into my ASHP / space heating controls and I am in awe of your ability to manipulate arrays. I have a thermal store of water which I want to heat when the electricity rate is lowest. I know how much heat I need from the Solcast temperature forecast and know the COP of my ASHP through the heating of the store and therfore know how long the ASHP will need to be on for ie how many half hour periods. I am using the ascending lowest cost periods but this leads to many stop starts for the ASHP. You mention "You have to hard code the X number in the function node, or bring this value from the dashboard, if you develop one.". I have the X number but cannot see how I can pass this parameter to either the batch node or the change node to establish the start and end of the lowest cost contiguous X periods. Can you please give me some guidance?

I am not sure that I understand the issues with a scheduler but I use the ui-etable. This uses an array of start times for either on/off or temperature periods. Each element of the array can be adjusted via the UI or via a function node to change the array. The array is then interrogated every 30 seconds to see what the state/temperature should be according to the current time and action take accordingly.
ScreenHunter_60 Dec. 23 13.11
UI appearance