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!
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"]]}]