BlueTech

Teknik

Shelly – Live elpris i shelly appen v.1

Ved at køre et script på en shelly enhed kan vi få live elpriser direkte i appen.
Så får du nemt et overblik over de præcise omkostninger for dit forbrug.

Hvad skal der bruges

  • Du skal bruge en shelly enhed, der understøtter scripting (Gen2 eller nyere)
  • Du skal have mindst en enhed der måler effekt (PM eller EM)
  • Brug shelly smart control appen eller http://control.shelly.cloud på din pc, det anbefales og det er også det jeg benytter i denne guide.
  • Hvis man vil bruge funktionen Virtuelle enheder, så kræves det som minimum en Gen3 eller nyere.

Kopier nedenstående script

// Henter data fra https://stromligning.dk
// 
 
// ===== KONFIGURATION =====
let CONFIG = {
  postalCode: "4720",               // Indtast postnummer (det eneste, du manuelt skal angive)
  additionalCost: 0.00,             // Yderligere omkostninger (kr/kWh), som lægges oveni price.total
  check_every_minute: false,        // true = tjek prisen hvert minut, false = tjek prisen hver time
  check_delay_seconds: 10,          // Antal sekunder efter time-/minutskiftet, hvorefter prischecket udføres
  post_url: "https://shelly-147-eu.shelly.cloud/v2/user/pp-ltu/eyJpZCI6MjA3MTUzOtokeng",// (Valgfrit) URL til POST – fjern eller tilpas, hvis ikke relevant
 
  vc_id: 200                        // (Valgfrit) ID på den virtuelle enhed, hvis prisen skal vises i Shelly GUI
};
 
let current_price = null;            // Her gemmes den aktuelle pris (til reference)
let loggingIntervalTimer = null;     // ID på gentaget log-timer (bruges til nedtælling)
let nextUpdateInMs = 0;              // Tid til næste update (i ms)
 
// Hjælpefunktion: Formaterer millisekunder til "0h 5m 10s"
function formatDelay(delayMs) {
  let totalSeconds = Math.floor(delayMs / 1000);
  let hours = Math.floor(totalSeconds / 3600);
  let minutes = Math.floor((totalSeconds % 3600) / 60);
  let seconds = totalSeconds % 60;
  return hours + "h " + minutes + "m " + seconds + "s";
}
 
// Enkel URL-encoding: erstatter kolon (:) med "%3A"
// (Espruino understøtter ikke regulære udtryk i denne version)
function myEncodeURIComponent(str) {
  return str.split(":").join("%3A");
}
 
/**
 * Henter den aktuelle pris ved at slå først supplier op (ud fra postnummer)
 * og derefter hente prisdata for den aktuelle time.
 * Resultatet (price.total + additionalCost) vises og sendes evt. videre.
 */
function getCurrentPrice() {
  // 1. HENT SUPPLIER-DATA
  let supplierUrl = "https://stromligning.dk/api/suppliers/find?postalCode=" + CONFIG.postalCode;
  print("Henter supplier-data fra: " + supplierUrl);
 
  Shelly.call("HTTP.GET", { url: supplierUrl }, function(supplierResult, supplierErrorCode, supplierErrorMessage) {
    if (supplierErrorCode !== 0) {
      print("Fejl ved supplier API GET: " + supplierErrorMessage);
      scheduleNextPriceCheck();
      return;
    }
 
    let suppliers = JSON.parse(supplierResult.body);
    if (!suppliers || suppliers.length === 0) {
      print("Ingen supplier-data modtaget.");
      scheduleNextPriceCheck();
      return;
    }
 
    // Brug den første supplier fra resultatet
    let supplier = suppliers[0];
    let supplierId = supplier.id;
    let priceArea  = supplier.priceArea;
    print("Fundet supplier: " + supplierId + ", priceArea: " + priceArea);
 
    // 2. HENT PRIS-DATA FOR NUVÆRENDE TIME
    let now = new Date();
    let year  = now.getFullYear();
    let month = ("0" + (now.getMonth() + 1)).slice(-2);
    let day   = ("0" + now.getDate()).slice(-2);
    let hour  = ("0" + now.getHours()).slice(-2);
 
    // Byg tidsstrengen for den aktuelle time (eksempel: "2025-02-02T15:00:00")
    let fromDate = year + "-" + month + "-" + day + "T" + hour + ":00:00";
    let toDate   = fromDate; // Vi henter kun data for den aktuelle time
 
    // URL-encoder dato-strengene
    let encodedFrom = myEncodeURIComponent(fromDate);
    let encodedTo   = myEncodeURIComponent(toDate);
 
    // Byg URL'en til pris-API'en
    let priceUrl = "https://stromligning.dk/api/prices?from=" + encodedFrom +
                   "&to=" + encodedTo +
                   "&supplierId=" + supplierId +
                   "&priceArea=" + priceArea;
    print("Henter price-data fra: " + priceUrl);
 
    Shelly.call("HTTP.GET", { url: priceUrl }, function(priceResult, priceErrorCode, priceErrorMessage) {
      if (priceErrorCode !== 0) {
        print("Fejl ved price API GET: " + priceErrorMessage);
        scheduleNextPriceCheck();
        return;
      }
 
      let data = JSON.parse(priceResult.body);
      if (!data.prices) {
        print("Ingen prisdata modtaget.");
        scheduleNextPriceCheck();
        return;
      }
 
      // Find posten for den nuværende time
      let currentHour = now.getHours();
      let found = false;
      data.prices.forEach(function(item) {
        let itemDate = new Date(item.date);
        let itemHour = itemDate.getHours();
        if (itemHour === currentHour) {
          found = true;
          // Brug API'ets price.total (allerede inkl. moms)
          let priceTotal = item.price.total;
          // Læg evt. yderligere omkostninger oveni
          let finalPrice = priceTotal + CONFIG.additionalCost;
          // Afrund til 2 decimaler
          current_price = finalPrice.toFixed(2);
          print("Price total for den nuværende time (" + currentHour + ":00): " +
                current_price + " " + item.price.unit +
                " (inkl. yderligere omkostninger: " + CONFIG.additionalCost.toFixed(2) + " kr/kWh)");
 
          // (Valgfrit) Skriv prisen til en virtuel enhed i Shelly GUI 
          Shelly.call("Number.Set", { id: CONFIG.vc_id, value: current_price });
 
          // (Valgfrit) POST prisen til en ekstern URL
          print("Posting price: " + current_price);
          Shelly.call("HTTP.REQUEST", {
            method: "POST",
            url: CONFIG.post_url,
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ price: current_price }),
            timeout: 5000,
          }, function(postResult, postErrorCode, postErrorMessage) {
            if (postErrorCode !== 0) {
              print("Fejl ved POST af pris: " + postErrorMessage);
            } else {
              print("Price posted successfully: " + current_price);
            }
          });
        }
      });
 
      if (!found) {
        print("Ingen data fundet for den nuværende time (" + currentHour + ":00).");
      }
 
      // Planlæg næste prisopdatering
      scheduleNextPriceCheck();
    });
  });
}
 
/**
 * Planlægger næste pris-tjek (enten hvert minut eller én gang i timen),
 * med en ekstra forsinkelse (CONFIG.check_delay_seconds).
 */
function scheduleNextPriceCheck() {
  let now = new Date();
  let delay = 0;
 
  if (CONFIG.check_every_minute === true) {
    // HVERT MINUT: beregn hvor mange ms til næste hele minut + ekstra forsinkelse
    let secondsUntilNextMinute = 60 - now.getSeconds();
    let msUntilNextMinute = (secondsUntilNextMinute * 1000) - now.getMilliseconds();
    delay = msUntilNextMinute + (CONFIG.check_delay_seconds * 1000);
    print("Scheduling next price check (minute-mode) in " + formatDelay(delay));
  } else {
    // HVERT TIME: beregn hvor mange ms til næste hele time + ekstra forsinkelse
    let totalSecsNow = now.getMinutes() * 60 + now.getSeconds();
    let msUntilNextHour = (3600 - totalSecsNow) * 1000 - now.getMilliseconds();
    delay = msUntilNextHour + (CONFIG.check_delay_seconds * 1000);
    print("Scheduling next price check (hour-mode) in " + formatDelay(delay));
  }
 
  nextUpdateInMs = delay;
 
  Timer.set(delay, false, function () {
    getCurrentPrice();
  });
 
  // Starter en log-timer, der hvert 10. minut udskriver, hvor lang tid der er til næste opdatering
  if (loggingIntervalTimer) {
    Timer.clear(loggingIntervalTimer);
    loggingIntervalTimer = null;
  }
  let tenMinutesInMs = 10 * 60 * 1000;
  loggingIntervalTimer = Timer.set(tenMinutesInMs, true, function () {
    nextUpdateInMs -= tenMinutesInMs;
    if (nextUpdateInMs <= 0) {
      print("No time left to next update (it should happen now).");
      Timer.clear(loggingIntervalTimer);
      loggingIntervalTimer = null;
    } else {
      print("Time left to next update: " + formatDelay(nextUpdateInMs));
    }
  });
}
 
// START: Test prisen med det samme, når scriptet starter
getCurrentPrice();

Indsæt scriptet ved at vælge en enhed og tryk på script ikonet

Opret nyt script, giv det et navn og sæt det kopierede script ind, gem scriptet..
Note: scriptet kan også nemt sættes ind fra enhedens web interface.

Så skal vi have fundet din Token

Gå til energi og vælg fanebladet Elpris, tryk på live ud for Elpris og find din Token API URL kopier denne..
OBS! Hvis man trykker på manuelt og live igen, er der generet en ny token, som skal skrives ind i scriptet.

Så indsætter du din Token, erstatter den nuværende token med din egen på linie 18
OBS! Gåseøjne skal stadig være der

Så skal scriptet rettes lidt til så det passer til den elpris du køber til

Nyttige links til at tjekke dine priser

Tjek dine priser og find transmission og tarif omkostninger her:
https://stromligning.dk/live

Find dine tariffer her:
https://tariffer.dk

Også en god side til at tjekke priser (især på mobil)
https://elpris.wen.dk

Script beskrivelse

For at få Scriptet konfigureret til dine priser, skal der ændres lidt.

price_area: DK1, eller DK2
Her skrives, hvilken priszone du er i.
DK1 for Jylland og Fyn.
DK2 for Sjælland.

include_vat: true, eller false
Her skrives om du vil have vist prisen med moms.
true for at inkludere moms (25%) i prisen.
false for at ekskludere moms.

include_elafgift: true, eller false
Her skrives om du vil have vist prisen med elafgift.
true for at inkludere elafgift i prisen.
false for at ekskludere elafgift.

elafgift: værdi   (Ex. 0.72)
Skriv elafgift i kroner (u.moms) per kWh. Hvis include_vat er true, lægges moms også oven i denne afgift.
(Er start 2025: Ex. moms = 0.72 kr. Inkl. moms =90 kr. )

additional_cost: værdi   (Ex. 0.168)
Her indtastes dine transmision omkostninger Ekstra omkostning pr. kWh i kroner. Bliver lagt oveni den endelige pris.
(hvis du har yderligere omkostninger lægges det bare oven i samme beløb).

tariff_periods:
Her indtastes de forskellige tidsperioder for tarifferne og deres priser. Linjer kan sagtens fjerne, eller tilføjes.
Ex. på en linje:
{ start: “00:01”, end: “06:00”, tariff: 0.14 },

check_every_minute: true, eller false
Her skrives om du vil have den til at hente prisen , hver time, eller, hvert minut.
true for at hente pris hvert minut (Bør kun bruges til test).
false for at hente pris, hver time (Anbefales).
(For ikke at belaste server, hvor prisen hentes bør 1 minut check kun bruges til at teste om det virker).

hour_check_delay_seconds:
  værdi  (ex 10)
Her skrives, antal sekunder efter timeskiftet, hvor prischecket udføres.

Sørg for at scriptet kører

Til sidst skal vi have scriptet til at køre og til at starte automatisk op, hvis enheden genstartet

Aktiver autostart og start ved at trykke på play knappen (Blå ring = aktiv)

Afslutning og test af script

Når scriptet har hentet prisen første gang skal du kunne se den under Energi -> Elpris det er nødvendig at opdaterer siden

OBS! Hvis man tilgår scriptet fra enhedens webinterface, så vil man kunne se log på hvad der sker.

Ekstra muligheder kommer i næste indlæg