1. Forside
  2. Scripts
  3. Billigste timer i træk

Scripts

Billigste timer i træk

Vælg hvor mange timer en belastning skal køre, og hvornår den skal være færdig. Enheden finder de billigste timer i træk inden da og melder, hvornår perioden starter og slutter.

Opdateret: september 2026

Før du går i gang

Kun i Danmark. Prisen kommer fra stromligning.dk og er den pris, kunden rent faktisk betaler: spotpris plus netselskabets tariffer og afgifter, fundet ud fra adressen. Skal der bare styres efter en prisgrænse, så brug Aktuelle elpriser i stedet.

  • En enhed, der understøtter virtuelle komponenter — Gen3 eller nyere, eller en Pro-model fra Gen2
  • Firmware 2.0.0 eller nyere. Scriptet opretter selv sine komponenter, og det kan ældre firmware ikke
  • Internetadgang på enheden, så den kan hente priserne
1
Script

Sæt scriptet ind

Opret et nyt script på enheden, sæt koden nedenfor ind, og gem. How to add a script (på engelsk) viser, hvor du finder det i appen og i webinterfacet.

Siden Scripts i webinterfacet på en Shelly Pro 3EM, endnu uden scripts, med knappen Create script øverst til højre
Scripts → Create script i enhedens webinterface.
/* @meta {
  "vc": {
    "price":  {"type": "number",  "config": {"name": "Price now", "min": 0, "max": 20, "meta": {"ui": {"view": "label", "unit": "DKK/kWh"}}}},
    "hours":  {"type": "number",  "config": {"name": "Hours in a row", "min": 1, "max": 12, "default_value": 3, "persisted": true, "meta": {"ui": {"view": "slider", "step": 1, "unit": "h"}}}},
    "ready":  {"type": "number",  "config": {"name": "Ready by (hour)", "min": 0, "max": 23, "default_value": 7, "persisted": true, "meta": {"ui": {"view": "slider", "step": 1, "unit": ":00"}}}},
    "active": {"type": "boolean", "config": {"name": "Cheap window now", "meta": {"ui": {"view": "label"}}}},
    "plan":   {"type": "text",    "config": {"name": "Plan", "meta": {"ui": {"view": "label"}}}},
    "avg":    {"type": "number",  "config": {"name": "Average price in window", "min": 0, "max": 20, "meta": {"ui": {"view": "label", "unit": "DKK/kWh"}}}},
    "group":  {"type": "group",   "config": {"name": "Cheapest hours"}}
  }
} */
// Cheapest hours in a row — Danish delivered price from stromligning.dk
// (spot + grid tariffs + taxes). Requires firmware 2.0.0 or newer.
// The script creates its own virtual components and puts them in a group.
// It switches nothing itself: "Cheap window now" is true while the cheapest
// window is running, and scenes or local actions switch the load on it.
// ===== CONFIGURATION =====
let CONFIG = {
  lat: "55.6736",         // Latitude
  long: "12.5681",        // Longitude
  additional_cost: 0.00,  // Extra cost from your electricity supplier, DKK/kWh
  fetch_delay_seconds: 10, // Seconds after each full hour the prices are fetched
  debug: false            // true = detailed log in the console, for troubleshooting
};
// ===== END OF CONFIGURATION =====
let H = 3600000;
let KVS_KEY = "cheapest_last_deadline";
print("Start");
let vPrice = Script.getVcHandle("price");
let vHours = Script.getVcHandle("hours");
let vReady = Script.getVcHandle("ready");
let vActive = Script.getVcHandle("active");
let vPlan = Script.getVcHandle("plan");
let vAvg = Script.getVcHandle("avg");
let vGroup = Script.getVcHandle("group");
let supplier = null;   // { id, area }
let prices = [];       // Hourly delivered prices, DKK/kWh
let firstTs = 0;       // Start of prices[0], ms
let plan = null;       // { start, end, avg, deadline }
let lastDeadline = 0;  // Deadline served by the last finished window. Kept in KVS across restarts
let waiting = false;   // True while tomorrow's prices are needed but not yet published
let wasActive = null;  // Last state written to the boolean
function pad2(n) {
  return ("0" + n).slice(-2);
}
function round2(v) {
  return Math.round(v * 100) / 100;
}
// Price components are limited to 0–20 DKK/kWh, so a value outside is clamped
function clampPrice(v) {
  return Math.min(20, Math.max(0, v));
}
function hourStart(ts) {
  return Math.floor(ts / H) * H;
}
function hhmm(ts) {
  return pad2(new Date(ts).getHours()) + ":00";
}
function dayHour(ts) {
  let d = new Date(ts);
  return d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate()) + " " + hhmm(ts);
}
// ----- Debug: writes only, never changes a decision. Every caller checks
// CONFIG.debug first, so with debug off no debug string is even built -----
function dbg(s) {
  print("[debug] " + s);
}
function dbgTime(ts) {
  let d = new Date(ts);
  return d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate()) + " " +
    pad2(d.getHours()) + ":" + pad2(d.getMinutes());
}
function dbgCoord(v) {
  return Math.round(parseFloat(v) * 100) / 100;
}
// Prices as "18:2.31 19:2.10 …", at most 12 hours per line
function dbgPrices() {
  let line = "";
  for (let i = 0; i < prices.length; i++) {
    line += pad2(new Date(firstTs + i * H).getHours()) + ":" + prices[i].toFixed(2) + " ";
    if ((i + 1) % 12 === 0 || i === prices.length - 1) {
      dbg("prices " + line.trim());
      line = "";
    }
  }
}
function dbgStart() {
  let info = Shelly.getDeviceInfo();
  let sys = Shelly.getComponentConfig("sys");
  dbg("firmware " + (info ? (info.ver || info.fw_id) : "?") + ", model " + (info ? info.model : "?"));
  dbg("time zone " + (sys && sys.location ? sys.location.tz : "?") + ", local time " + dbgTime(Date.now()));
  dbg("config lat " + dbgCoord(CONFIG.lat) + ", long " + dbgCoord(CONFIG.long) +
    ", additional_cost " + CONFIG.additional_cost + ", fetch_delay_seconds " + CONFIG.fetch_delay_seconds);
}
// Never calls a handle that may be null, and never lets a failed write stop the script
function setVc(h, v) {
  if (!h) return;
  try {
    h.setValue(v);
  } catch (e) {
    print("Could not write a component: " + e.message);
  }
}
function getVc(h, fallback) {
  if (!h) return fallback;
  let v = h.getValue();
  return typeof v === "number" ? v : fallback;
}
// The script engine has no regular expressions, so colons are replaced by hand
function stamp(ts, minutes) {
  let d = new Date(ts);
  let s = d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate()) +
    "T" + pad2(d.getHours()) + ":" + minutes + ":00";
  return s.split(":").join("%3A");
}
function hoursWanted() {
  let n = Math.round(getVc(vHours, 3));
  if (!(n >= 1)) n = 1;
  if (n > 12) n = 12;
  return n;
}
function readyHour() {
  let h = Math.round(getVc(vReady, 7));
  if (!(h >= 0)) h = 0;
  if (h > 23) h = 23;
  return h;
}
// Average price between two hour boundaries, or null if a price is missing
function avgOf(start, end) {
  let i = (start - firstTs) / H;
  let n = (end - start) / H;
  if (i < 0 || i + n > prices.length) return null;
  let sum = 0;
  for (let j = 0; j < n; j++) sum += prices[i + j];
  return round2(sum / n);
}
// Next time the load has to be done. Skips the deadline the last window
// already served, so a window ending at 05:00 does not start a new one before 07:00
function nextDeadline(now) {
  let d = new Date(now);
  let dl = new Date(d.getFullYear(), d.getMonth(), d.getDate(), readyHour(), 0, 0).getTime();
  while (dl <= now || dl <= lastDeadline) dl += 24 * H;
  return dl;
}
function computePlan() {
  let now = Date.now();
  let n = hoursWanted();
  let deadline = nextDeadline(now);
  // The current hour only counts if we are within its first five minutes
  let from = hourStart(now);
  if (now - from > 5 * 60000) from += H;
  let latestStart = deadline - n * H;
  if (latestStart < from) latestStart = from; // Not enough time left: start as soon as possible
  let knownEnd = firstTs + prices.length * H;
  // Tomorrow's prices are published around 13:00. If the window reaches past
  // the known prices and there is still time, wait for them instead of guessing
  if (CONFIG.debug) dbg("compute: now " + dbgTime(now) + ", hours " + n + ", deadline " + dbgTime(deadline) +
    ", from " + dbgTime(from) + ", latestStart " + dbgTime(latestStart) + ", knownEnd " + (knownEnd ? dbgTime(knownEnd) : "none"));
  if (latestStart + n * H > knownEnd && from < latestStart) {
    if (CONFIG.debug) dbg("waiting: the window may end at " + hhmm(latestStart + n * H) +
      ", prices are known until " + (knownEnd ? hhmm(knownEnd) : "none") + ", and there is still time");
    plan = null;
    waiting = true;
    return;
  }
  waiting = false;
  let best = -1;
  let bestAvg = 0;
  let top = [];  // The three cheapest candidates, for debug only
  for (let s = from; s <= latestStart; s += H) {
    let a = avgOf(s, s + n * H);
    if (a !== null && (best < 0 || a < bestAvg)) {
      best = s;
      bestAvg = a;
    }
    if (CONFIG.debug && a !== null) {
      top.push({ s: s, a: a });
      top.sort(function (x, y) { return x.a - y.a; });
      if (top.length > 3) top.pop();
    }
  }
  if (CONFIG.debug) {
    let t = "";
    for (let k = 0; k < top.length; k++) t += hhmm(top[k].s) + "-" + hhmm(top[k].s + n * H) + " " + top[k].a.toFixed(2) + "  ";
    dbg("cheapest candidates: " + (t || "none"));
  }
  if (best < 0) {
    // No usable prices and no time left: run now so the load is still done on time
    print("No prices for the window — starting now as a fallback");
    plan = { start: from, end: from + n * H, avg: null, deadline: deadline };
  } else {
    plan = { start: best, end: best + n * H, avg: bestAvg, deadline: deadline };
  }
  print("Plan: " + hhmm(plan.start) + "-" + hhmm(plan.end) + ", average " + plan.avg + " DKK/kWh");
  if (CONFIG.debug) dbg("chosen: " + dbgTime(plan.start) + " to " + dbgTime(plan.end));
}
function show() {
  if (plan) {
    let text = hhmm(plan.start) + " → " + hhmm(plan.end);
    if (plan.avg !== null) {
      text += " (" + plan.avg.toFixed(2) + " DKK)";
      setVc(vAvg, clampPrice(plan.avg));
    }
    setVc(vPlan, text);
  } else {
    setVc(vPlan, waiting ? "Waiting for tomorrow's prices" : "No plan");
  }
}
// Written only when a window ends, so a restart before the deadline does not run the load again
function saveLastDeadline() {
  if (CONFIG.debug) dbg("KVS write " + KVS_KEY + " = " + lastDeadline + " (" + dbgTime(lastDeadline) + ")");
  Shelly.call("KVS.Set", { key: KVS_KEY, value: lastDeadline }, function (res, code, msg) {
    if (code !== 0) print("Could not save the last deadline: " + msg);
    if (code !== 0 && CONFIG.debug) dbg("KVS write failed: code " + code + ", " + msg);
  });
}
function evaluate() {
  let now = Date.now();
  if (plan && now >= plan.end) {
    lastDeadline = plan.deadline;
    saveLastDeadline();
    plan = null;
  }
  // A window that has started is locked. Anything else is recalculated,
  // so newly published prices are used right away
  if (!(plan && now >= plan.start)) computePlan();
  let on = plan !== null && now >= plan.start && now < plan.end;
  // Only written on changes, so scenes and actions fire once per start and end
  if (on !== wasActive) {
    setVc(vActive, on);
    let was = wasActive;
    wasActive = on;
    print(on ? "Cheap window started" : "Cheap window ended");
    if (CONFIG.debug && on) dbg("window started and locked: " + dbgTime(plan.start) + " to " + dbgTime(plan.end));
    if (CONFIG.debug && !on && was === true) dbg("window ended at " + dbgTime(now));
  }
  show();
}
function safeEvaluate() {
  try {
    evaluate();
  } catch (e) {
    print("Evaluate failed: " + e.message);
  }
}
function onPrices(res, code, msg) {
  try {
    if (CONFIG.debug) dbg("price fetch: rpc code " + code + ", HTTP " + (res ? res.code : "none") +
      ", " + (res && res.body ? res.body.length : 0) + " characters" + (code !== 0 ? ", error " + msg : ""));
    if (code !== 0 || !res || res.code !== 200) {
      print("Price fetch failed: " + (msg || (res ? res.code : "")) + " — keeping the prices already known");
    } else {
      let data = JSON.parse(res.body);
      res = null;
      let list = data.prices ? data.prices : data;
      let p = [];
      let t0 = 0;
      for (let i = 0; i < list.length; i++) {
        let item = list[i];
        let ts = new Date(item.date).getTime();
        let v = typeof item.price === "number" ? item.price : item.price.total;
        if (i === 0) t0 = ts;
        if (ts !== t0 + p.length * H) {
          print("Gap in the price data — using the prices before it");
          break;
        }
        p.push(v + CONFIG.additional_cost);
      }
      data = null;
      list = null;
      if (p.length > 0) {
        prices = p;
        firstTs = t0;
        print("Prices parsed: " + prices.length + " hours");
        print("Prices known until " + hhmm(firstTs + prices.length * H));
        if (CONFIG.debug) {
          dbg(prices.length + " hours in the answer, first " + dbgTime(firstTs) + ", last " + dbgTime(firstTs + (prices.length - 1) * H));
          dbgPrices();
        }
      }
    }
    let i = (hourStart(Date.now()) - firstTs) / H;
    if (i >= 0 && i < prices.length) setVc(vPrice, clampPrice(round2(prices[i])));
  } catch (e) {
    print("Could not read the prices: " + e.message);
    if (CONFIG.debug) dbg("price parse error: " + e.message);
  }
  safeEvaluate();
}
function fetchPrices() {
  let from = hourStart(Date.now());
  let url = "https://stromligning.dk/api/prices?from=" + stamp(from, "00") +
    "&to=" + stamp(from + 35 * H, "45") +
    "&supplierId=" + supplier.id + "&priceArea=" + supplier.area +
    "&aggregation=1h&lean=true";
  if (CONFIG.debug) dbg("fetch " + url);
  Shelly.call("HTTP.GET", { url: url, timeout: 15 }, onPrices);
}
function onSupplier(res, code, msg) {
  try {
    if (CONFIG.debug) dbg("grid company lookup: rpc code " + code + ", HTTP " + (res ? res.code : "none") +
      ", " + (res && res.body ? res.body.length : 0) + " characters" + (code !== 0 ? ", error " + msg : ""));
    if (code !== 0 || !res || res.code !== 200) {
      print("Grid company lookup failed: " + msg);
    } else {
      let list = JSON.parse(res.body);
      if (list && list.length > 0) {
        supplier = { id: list[0].id, area: list[0].priceArea };
        print("Grid company: " + supplier.id + ", price area " + supplier.area);
      } else {
        print("No grid company found for these coordinates — check lat and long");
      }
    }
    if (supplier) {
      fetchPrices();
      return;
    }
  } catch (e) {
    print("Could not read the grid company: " + e.message);
  }
  safeEvaluate();
}
function onTick() {
  try {
    if (supplier) {
      fetchPrices();
    } else {
      let url = "https://stromligning.dk/api/suppliers/find?lat=" + CONFIG.lat + "&long=" + CONFIG.long;
      if (CONFIG.debug) dbg("grid company lookup near lat " + dbgCoord(CONFIG.lat) + ", long " + dbgCoord(CONFIG.long));
      Shelly.call("HTTP.GET", { url: url, timeout: 15 }, onSupplier);
    }
  } catch (e) {
    print("Tick failed: " + e.message);
  }
  scheduleTick();
}
function scheduleTick() {
  let now = Date.now();
  let delay = hourStart(now) + H - now + CONFIG.fetch_delay_seconds * 1000;
  Timer.set(delay, false, onTick);
}
// Changing the hours during a running window extends or shortens it.
// Otherwise the plan is recalculated at once
function onHoursChange(ev) {
  try {
    let now = Date.now();
    if (plan && now >= plan.start) {
      let oldEnd = plan.end;
      plan.end = plan.start + hoursWanted() * H;
      plan.avg = avgOf(plan.start, plan.end);
      if (CONFIG.debug) dbg("running window " + (plan.end > oldEnd ? "extended" : "shortened") + ": " +
        dbgTime(plan.start) + " to " + dbgTime(plan.end) + " (was to " + dbgTime(oldEnd) + ")");
    } else {
      plan = null;
    }
  } catch (e) {
    print("Hours change failed: " + e.message);
  }
  safeEvaluate();
}
// A new deadline takes effect at once, unless a window is running
function onReadyChange(ev) {
  try {
    if (!(plan && Date.now() >= plan.start)) plan = null;
  } catch (e) {
    print("Deadline change failed: " + e.message);
  }
  safeEvaluate();
}
// Puts the six components in the script's own group, so the user only has
// to extract it as a device in the app
function fillGroup() {
  let parts = [["number", vPrice], ["number", vHours], ["number", vReady],
    ["boolean", vActive], ["text", vPlan], ["number", vAvg]];
  let keys = [];
  for (let i = 0; i < parts.length; i++) {
    let c = parts[i][1] ? parts[i][1].getConfig() : null;
    if (c && typeof c.id === "number") keys.push(parts[i][0] + ":" + c.id);
  }
  if (CONFIG.debug) dbg("components " + keys.join(", "));
  let g = vGroup ? vGroup.getConfig() : null;
  if (!g || typeof g.id !== "number") {
    print("Group missing — add the components to a group by hand");
    return;
  }
  Shelly.call("Group.Set", { id: g.id, value: keys }, function (res, code, msg) {
    print(code === 0 ? "Group filled: " + keys.join(", ") : "Could not fill the group: " + msg);
    if (code !== 0 && CONFIG.debug) dbg("Group.Set failed: code " + code + ", " + msg);
  });
}
function onLastDeadline(res, code, msg) {
  try {
    if (code === 0 && res && typeof res.value === "number") lastDeadline = res.value;
    if (CONFIG.debug) dbg("KVS " + KVS_KEY + " = " + (code === 0 && res ? res.value : "not set (code " + code + ")"));
    print("Last deadline served: " + (lastDeadline ? dayHour(lastDeadline) : "none"));
  } catch (e) {
    print("Could not read the last deadline: " + e.message);
  }
  onTick();
}
function start() {
  if (!vHours || !vReady || !vActive || !vPlan || !vAvg || !vPrice) {
    print("Virtual components missing — the device needs firmware 2.0.0 or newer");
    return;
  }
  print("Components found");
  if (CONFIG.debug) {
    try {
      dbgStart();
    } catch (e) {
      dbg("start info failed: " + e.message);
    }
  }
  try {
    fillGroup();
  } catch (e) {
    print("Could not fill the group: " + e.message);
  }
  vHours.on("change", onHoursChange);
  vReady.on("change", onReadyChange);
  Shelly.call("KVS.Get", { key: KVS_KEY }, onLastDeadline);
}
try {
  start();
} catch (e) {
  print("Start failed: " + e.message);
}
2
Opsætning

Skriv koordinaterne ind

Gå i enhedens indstillinger (tandhjulet), åbn afsnittet om placering og tidszone, og vælg placeringen på kortet. Træk markøren hen i nærheden af adressen, og bekræft.

Skriv breddegraden i lat og længdegraden i long øverst i scriptet. Begge tal står i gåseøjne, og det skal de blive ved med. Det er nok, at markøren ligger i området. Kun hvis kunden bor lige på grænsen mellem to netselskaber, skal den være præcis.

Tjek derefter resten af blokken CONFIG, og gem:

Script-editoren med scriptet sat ind, erklæringen af de virtuelle komponenter øverst og blokken CONFIG nedenunder med lat, long, additional_cost og fetch_delay_seconds
Blokken CONFIG, med koordinaterne sløret.
Indstilling Standard Hvad den gør
lat, long København Hvor enheden står. Afgør netselskab og prisområde
additional_cost 0.00 Elselskabets eget tillæg i kr/kWh. Lægges oven i hver time
fetch_delay_seconds 10 Hvor mange sekunder efter hvert timeskift priserne hentes
debug false true skriver en udførlig log i konsollen til fejlfinding. Lad den ellers stå på false
3
Start

Start scriptet

Slå autostart til, og start scriptet. Uden autostart stopper det ved første strømafbrydelse og kommer ikke igen af sig selv.

Efter få sekunder har scriptet oprettet seks komponenter på enheden — Price now, Hours in a row, Ready by (hour), Cheap window now, Plan og Average price in window — og samlet dem i en gruppe, der hedder Cheapest hours. Navnene er engelske, fordi de kommer fra scriptet, og det er dem, du skal kigge efter i appen.

Script-listen med et script, der er sat til at starte automatisk
Listen viser både, om scriptet kører nu, og om det starter igen af sig selv.
Enhedens brugerdefinerede komponenter: Cheap window now, Average price in window, Price now og Plan vist som label, Hours in a row og Ready by (hour) som slider, hver i én gruppe
De seks komponenter under Components. Værdierne, scriptet skriver, er labels og kan ikke ændres; de to indstillinger er sliders. Tælleren viser 0 for komponenter, et script ejer.
Enhedens brugerdefinerede grupper med én gruppe, Cheapest hours, der indeholder de seks komponenter
Gruppen Cheapest hours, fyldt af scriptet.
4
App

Pak gruppen ud som enhed

Komponenterne dukker først op i appen, når de ligger i en gruppe, der er pakket ud som enhed. Indtil da findes de kun på selve enheden.

Åbn enhedens Indstillinger i appen → Virtuelle komponenter → Grupper. Tryk på linket under Cheapest hours for at pakke gruppen ud som enhed (extract device i den engelske app). Så står de seks komponenter samlet som én enhed på forsiden, og scenerne kan bruge dem.

Enhedens indstillinger i appen under Virtual components og Groups, med gruppen Cheapest hours, linket extract device og de seks komponenter med deres værdier
Indstillinger → Virtuelle komponenter → Grupper → extract device. Vist i den engelske app.
5
Brug

Vælg antal timer og sluttidspunkt

  • Hours in a row — hvor mange timer belastningen skal køre, 1 til 12
  • Ready by (hour) — hvornår den skal være færdig, 0 til 23. 7 betyder færdig kl. 07:00

Plan viser så den periode, scriptet har valgt, for eksempel 01:00 → 06:00 (1.86 DKK), med gennemsnitsprisen. Begge indstillinger overlever en genstart.

Appens forside med den udpakkede enhed Cheapest hours åbnet: Price now, Hours in a row sat til 5 h, Ready by (hour) sat til 6:00, Cheap window now False, Plan 01:00 til 06:00 til 1,86 kr og gennemsnitsprisen
Den udpakkede enhed i appen.
6
Kontrol

Tjek konsollen

Åbn scriptet fra enhedens webinterface, og kig i konsollen. Inden for et minut skal der stå:

  • Start, Components found og Group filled — scriptet kører og har sine komponenter
  • Grid company — netselskab og prisområde, fundet ud fra koordinaterne
  • Prices parsed og Prices known until — hvor mange timer der er hentet, og hvor langt frem de rækker
  • Plan — den valgte periode og dens gennemsnitspris, så snart der er en
Scriptets konsol med linjerne Start, Components found og Group filled efterfulgt af de seks komponenters nøgler
De første tre linjer, lige efter start.

Linjerne kommer i den rækkefølge, så den sidste viser, hvor langt scriptet nåede. Står der “No grid company found”, er koordinaterne forkerte. Er konsollen tom, er websocket debug som regel slået fra — se How to add a script (på engelsk).

Brug perioden i en scene

Cheap window now er sand, mens den billigste periode kører. Der skal to scener (på engelsk) til: én, der starter belastningen, når Cheap window now bliver on, og én, der stopper den, når Cheap window now bliver off. Belastningen kan sidde på en hvilken som helst Shelly, ikke kun den, scriptet kører på.

En scene i appen: Når Cheapest hours, Cheap window now On, udløser Once; gør en elbillader, Charging On; aktiv hele døgnet, alle ugens dage
Scenen, der starter opladningen. Den, der stopper den, er magen til, bare med Off.

Fjern løsningen igen

  • Slet scriptet, mens det kører. Så forsvinder komponenterne og gruppen sammen med det.
  • Er scriptet stoppet, så start det først, og slet det derefter. Sletter du et stoppet script, kan komponenterne blive liggende og være låst, så de ikke kan slettes i hånden.
  • Slet selv de scener, der bruger Cheap window now. De forsvinder ikke med scriptet.

Sådan vælger scriptet

  • Perioden slutter altid senest på sluttidspunktet. Scriptet vælger de billigste timer i træk, der kan nå at blive færdige inden Ready by.
  • Morgendagens priser kommer omkring kl. 13. Kan perioden række ind i morgendagen, står der Waiting for tomorrow’s prices i Plan, indtil de er der.
  • En periode, der er gået i gang, ligger fast. Nye priser flytter den ikke.
  • En genstart kører ikke belastningen to gange. Enheden husker, at den seneste periode allerede er kørt, så en strømafbrydelse bagefter ikke starter en ny inden sluttidspunktet.
  • Ændrer du Hours in a row undervejs, bliver den kørende periode længere eller kortere.
  • Uden priser starter scriptet så sent, at det stadig når at blive færdigt. Belastningen bliver kørt, selv om et opslag slår fejl.
  • Scriptet tænder og slukker ikke selv noget. Cheap window now skifter én gang ved start og én gang ved slut, og det er scenen, der tænder og slukker. Tænder eller slukker du i hånden ind imellem, holder det til næste start eller slut.

Kører du allerede elpris-scriptet?

Dette script afløser det gamle. Stop det gamle script, så enheden ikke henter prisen to gange — Price now viser den samme leverede pris. Det eneste, det gamle script kan, som dette ikke kan, er at sende prisen videre til appens egen elprisvisning.

En enhed kan have højst 10 virtuelle komponenter. Scriptet opretter syv: seks komponenter og deres gruppe. Slet den komponent og gruppe, du lavede til det gamle script, hvis der ikke er plads.