Live Elpris – Svenska
- Home
- Live Elpris – Svenska
Visa dina rörliga elpriser i Shelly-appen
Vad man ska använda
- Du behöver en Shelly-enhet som stöder skript (Gen2 eller senare).
- Du måste också ha minst en enhet som mäter effekt (PM eller EM).
- Använd Shelly Smart Control-appen på Android, iOS eller http://control.shelly.cloud på din PC. Det rekommenderas, och det är också det jag använder i den här guiden.
Steg 1: Kopiera Script
Kopiera scriptet nedan
(När du håller musen över den kan du välja Kopiera i det övre högra hörnet)
// =====================================================================
// Shelly script: hämtar dagens elpris (SE1) en gång per dygn och postar
// aktuell kvart till Shelly Cloud — en post per kvart, på exakta
// klockslag xx:00:01, xx:15:01, xx:30:01, xx:45:01.
//
// API:et levererar kvartspriser (96 poster per dygn), och varje tick
// plockar ut posten vars time_start motsvarar nuvarande HH:MM-kvart.
//
// Hämtningen sker bara när cachen är tom eller dagen har bytt (alltså
// vid första ticken efter midnatt). Resten av dygnet återanvänds cachen.
// Elpriserna från API:et anges exklusive påslag från din elleverantör, avgift för elcertifikat, elskatt och moms samt exklusive nätbolagets avgifter.
// Detta skript lägger själv på dessa rörliga påslag (exkl. moms) och därefter moms, se konfigurationen nedan (VARIABLE_ADDITIONS_EXVAT och VAT_RATE). Det värde som postas till molnet är alltså det slutliga konsumentpriset.
// =====================================================================
// --- Konfiguration ---
let PRICE_API_BASE = "https://se.elpris.eu/api/v1/prices"; //Variabel för base-URL för elpris-API:et. Ändra inte denna, prisklassen läggs till i buildPriceUrl().
let PRICE_ZONE = "SE1"; // Ange SE1, SE2, SE3 eller SE4 - används i buildPriceUrl() för att bygga rätt URL till API:et. Måste matcha det som finns på se.elpris.eu.
let CLOUD_URL = "https://shelly-204-eu.shelly.cloud/v2/users/pp-ltu/........"; // URL:en att posta priset till. Byt ut mot din egen URL från Shelly Cloud (se instruktioner i README).
let HTTP_TIMEOUT_S = 20; // Timeout i sekunder för HTTP-anrop (både GET och POST).
// --- Prispåslag och moms (konfigureras av användaren) ---
// VIKTIGT: Alla belopp nedan MÅSTE anges i SAMMA enhet som spotpriset från
// API:et (SEK per kWh). Fyll i dina egna värden EXKLUSIVE moms från din
// elhandels- respektive nätfaktura. Sätt en post till 0 om den inte gäller dig.
//
// Exempel: om ditt påslag är 8,5 öre/kWh skriver du 0.085 (SEK/kWh).
// Rörliga påslag exkl. moms (SEK/kWh):
let ELLEVERANTOR_PASLAG = 0.0; // Påslag från din elleverantör (markup)
let ELCERTIFIKAT = 0.0; // Avgift för elcertifikat (certificates)
let OVERFORINGSAVGIFT = 0.0; // Nätbolagets överföringsavgift (transfer fee)
let ELSKATT = 0.0; // Energiskatt / elskatt (energy tax)
// Myndighetsavgifter (statliga avgifter SEK/kWh exkl. moms, tas normalt ut via nätbolaget):
let ELBEREDSKAPSAVGIFT = 0.0; // Elberedskapsavgift (electrical emergency preparedness)
let ELSAKERHETSAVGIFT = 0.0; // Elsäkerhetsavgift (electrical safety)
let NATOVERVAKNINGSAVGIFT = 0.0; // Nätövervakningsavgift (grid monitoring)
// (1) Summan av alla rörliga påslag ovan, exkl. moms. Läggs ovanpå spotpriset.
let VARIABLE_ADDITIONS_EXVAT =
ELLEVERANTOR_PASLAG +
ELCERTIFIKAT +
OVERFORINGSAVGIFT +
ELSKATT +
ELBEREDSKAPSAVGIFT +
ELSAKERHETSAVGIFT +
NATOVERVAKNINGSAVGIFT;
// (2) Moms (VAT). 0.25 = 25 %. Läggs ovanpå allt (spotpris + alla påslag).
let VAT_RATE = 0.25;
// --- Cache ---
// API-svaret ser ut så här:
// { src, t0: "YYYY-MM-DDTHH:MM:SS+TZ", s: 900, u: "SEK", raw: 96, p: [pris0, pris1, ...] }
// där s är stegtiden i sekunder (900 = 15 min) och p är priserna i ordning.
let cachedPrices = null; // hela svarsobjektet ovan
let cachedDateStr = null; // "YYYY-MM-DD" som cachen gäller för
// --- Reentrans-skydd ---
// En säkerhetsåtgärd som förhindrar att en funktion anropas igen innan det första anropet har körts klart, för att undvika parallella hämtningar/poster (används i fetchPrices och postPrice).
let isFetching = false;
let isPosting = false;
// =====================================================================
// Hjälpfunktioner
// =====================================================================
// Pad:ar en siffra till två tecken, t.ex. 7 -> "07". Detta för att formatet ska vara korrekt (kräver 2 tecken för månad och dag i datumsträngen).
function pad2(n) {
return (n < 10 ? "0" : "") + n;
}
// Returnerar dagens datum som "YYYY-MM-DD". Används för att jämföra med cachedDateStr och för att plocka ut rätt pris inför postPrice.
function todayStr() {
let d = new Date();
return d.getFullYear() + "-" + pad2(d.getMonth() + 1) + "-" + pad2(d.getDate());
}
// Bygger URL:en till elpris-API:et utifrån dagens datum och vald prisklass:
// https://se.elpris.eu/api/v1/prices/[ÅR]/[MÅNAD]-[DAG]_[PRISKLASS].json
function buildPriceUrl() {
let d = new Date();
let year = d.getFullYear();
let month = pad2(d.getMonth() + 1);
let day = pad2(d.getDate());
return PRICE_API_BASE + "/" + year + "/" + month + "-" + day + "_" + PRICE_ZONE + ".json";
}
// =====================================================================
// Hämta dagens priser och cacha
// =====================================================================
function fetchPrices(callback) { // callback får en boolean-parameter som indikerar om hämtningen lyckades eller inte. Detta är asynkront, så callback används för att veta när det är klart. Boolean-parametern ändras till true om hämtningen lyckades och cachen uppdaterades, eller false om det skedde något fel (t.ex. nätverksfel, API-fel, JSON-parsefel, oväntat svarsformat). Callback sätts i varje if-statement där det finns en exit-point, så att anroparen kan hantera både framgång och misslyckande på ett konsekvent sätt.
if (isFetching) { // isFetching kollar om den variabeln är true, vilket indikerar att en hämtning redan är på gång. Om så är fallet, printar den ett meddelande och anropar callback med false (om callback finns) för att indikera att hämtningen inte startades eftersom en annan redan pågår. Sedan returnerar den för att undvika att starta en ny hämtning.
print("Fetch redan igång, hoppar över.");
if (callback) callback(false);
return;
}
isFetching = true; // Sätter isFetching till true innan den går vidare med följande kod
let url = buildPriceUrl(); //Anropar funktionen buildPriceUrl() för att få den fullständiga URL:en till elpris-API:et och lagrar den i variabeln url
print("Hämtar dagens priser (" + PRICE_ZONE + ") från " + url);
// Använder befintlig API-funktionen Shelly.call() för att göra en HTTP GET-förfrågan till den URL som byggdes tidigare. Den skickar också med en timeout (HTTP_TIMEOUT_S) och callback-funktionen.
Shelly.call(
"HTTP.GET",
{ url: url, timeout: HTTP_TIMEOUT_S },
function (response, error_code, error_message) { // Callback-funktionen som kommer att köras när HTTP GET-förfrågan är klar. Den tar tre parametrar: response (svaret från servern), error_code (en kod som indikerar om det skedde ett fel under hämtningen, där 0 betyder ingen fel), och error_message (en textbeskrivning av felet, om det skedde något).
isFetching = false;
// Kollar först om error_code inte är 0 eller om response är falskt (vilket indikerar att det skedde ett fel under hämtningen). Om så är fallet, printar den ett meddelande med error_message och anropar callback med false (om callback finns) för att indikera att hämtningen misslyckades. Sedan returnerar den för att undvika att fortsätta med felaktiga data.
if (error_code !== 0 || !response) {
print("Hämtningsfel:", error_message);
if (callback) callback(false);
return;
}
// Kollar sedan om HTTP-statuskoden i response inte är 200 (vilket betyder att förfrågan då inte lyckades). Om så är fallet, printar den ett meddelande med statuskoden och anropar callback med false (om callback finns) för att indikera att hämtningen misslyckades. Sedan returnerar den för att undvika att fortsätta med felaktiga data.
if (response.code !== 200) {
print("HTTP-fel vid hämtning:", response.code);
if (callback) callback(false);
return;
}
// Försöker sedan att parsa response.body som JSON. Om det sker ett fel under parsningen (t.ex. om svaret inte är giltig JSON), fångar den felet, printar ett meddelande med felinformationen, och anropar callback med false (om callback finns) för att indikera att hämtningen misslyckades. Sedan returnerar den för att undvika att fortsätta med felaktiga data.
let parsed = null;
try {
parsed = JSON.parse(response.body);
} catch (e) {
print("JSON-parsefel:", e);
if (callback) callback(false);
return;
}
// Kollar sedan om det parsade objektet inte har en egenskap .p, eller om .p inte är en array med en längd som är ett nummer och större än 0. Om så är fallet, printar den ett meddelande om oväntat svarsformat och anropar callback med false (om callback finns) för att indikera att hämtningen misslyckades. Sedan returnerar den för att undvika att fortsätta med felaktiga data.
if (!parsed || !parsed.p || typeof parsed.p.length !== "number" || parsed.p.length === 0) {
print("Oväntat svarsformat (förväntade objekt med icke-tom .p-array).");
if (callback) callback(false);
return;
}
// Om allt gick bra, uppdaterar den cachen med det parsade objektet och sätter cachedDateStr till dagens datum (som returneras av todayStr()). Sedan printar den ett meddelande om att priserna har cachats, inklusive antalet priser som cachades, valutan (parsed.u) och stegtiden i sekunder (parsed.s). Slutligen anropar den callback med true (om callback finns) för att indikera att hämtningen och cachningen lyckades.
cachedPrices = parsed;
cachedDateStr = todayStr();
print("Cachade " + parsed.p.length + " priser (" + (parsed.u || "?") +
", steg " + (parsed.s || "?") + "s) för " + cachedDateStr + ".");
if (callback) callback(true);
}
);
}
// =====================================================================
// Hitta priset för aktuell kvart i cachen
// =====================================================================
// API:et ger priserna som en ordnad array .p med start vid lokal midnatt
// (.t0) och stegtid .s (sekunder). Vi räknar fram index från enhetens
// lokala klocka: floor(sekunder sedan midnatt / s).
function findCurrentPrice() {
if (!cachedPrices || !cachedPrices.p) return null; // Om cachen inte är uppdaterad eller inte har priser, returnera null.
let now = new Date();
let secSinceMidnight = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();
let step = cachedPrices.s || 900; // 900 s = 15 min
let idx = Math.floor(secSinceMidnight / step);
if (idx < 0 || idx >= cachedPrices.p.length) return null;
return cachedPrices.p[idx];
}
// =====================================================================
// Posta {"price": "X.XX"} till molnet
// =====================================================================
// Funktionen postPrice() tar ett prisvärde som argument och postar det till molnet i JSON-format. Den använder isPosting för att säkerställa att den inte startar en ny postning om en annan redan är på gång. Om isPosting är true, printar den ett meddelande och returnerar utan att posta. Om inte, sätter den isPosting till true och fortsätter med att bygga JSON-strängen och göra HTTP POST-anropet. När anropet är klart (oavsett om det lyckades eller inte), sätter den isPosting tillbaka till false så att nästa postning kan startas. Denna funktion anropas ifrån postCurrentQuarter() när det är dags att posta det aktuella kvartets pris, och den tar hand om att formatera priset som en sträng i JSON-objektet som skickas till molnet. Den loggar också resultatet av POST-anropet, inklusive eventuella felkoder eller meddelanden, för att underlätta felsökning.
function postPrice(priceValue) {
if (isPosting) {
print("Post redan igång, hoppar över.");
return;
}
isPosting = true;
let bodyStr = JSON.stringify({ price: String(priceValue) });
print("Postar pris: " + bodyStr);
Shelly.call(
"HTTP.POST",
{
url: CLOUD_URL,
body: bodyStr,
headers: { "Content-Type": "application/json" },
timeout: HTTP_TIMEOUT_S
},
function (res, err_c, err_m) {
isPosting = false;
if (err_c === 0 && res && res.code >= 200 && res.code < 300) {
print("POST OK.");
} else {
print("POST misslyckades. err_c=", err_c, "err_m=", err_m,
"code=", res ? res.code : "n/a");
}
}
);
}
// =====================================================================
// Räkna ut slutligt konsumentpris utifrån spotpriset
// =====================================================================
// Tar spotpriset (SEK/kWh) och lägger på (1) summan av alla rörliga påslag
// exkl. moms och sedan (2) moms på hela summan:
// slutpris = (spotpris + VARIABLE_ADDITIONS_EXVAT) * (1 + VAT_RATE)
function applyPriceComponents(spotPrice) {
return (spotPrice + VARIABLE_ADDITIONS_EXVAT) * (1 + VAT_RATE);
}
// =====================================================================
// Tick: körs på varje jämn kvart + 1s. Säkerställer att cachen är från
// idag (annars hämtas den om), plockar ut nuvarande kvarts pris och postar.
// =====================================================================
// Funktionen postCurrentQuarter() plockar ut priset för aktuell kvart från cachen och anropar postPrice() för att posta det till molnet. Om det inte finns något pris (t.ex. om cachen inte är uppdaterad eller om det är något fel), printar den ett meddelande och returnerar utan att posta.
function postCurrentQuarter() {
let spotPrice = findCurrentPrice();
if (typeof spotPrice !== "number") {
print("Hittade inget pris för aktuell kvart.");
return;
}
// Lägg på rörliga påslag (exkl. moms) och därefter moms på hela summan.
let finalPrice = applyPriceComponents(spotPrice);
print("Spotpris: " + spotPrice + " -> slutpris (inkl. påslag och moms): " + finalPrice);
postPrice(finalPrice);
}
// Funktionen tick() är den huvudsakliga funktionen som körs på varje jämn kvart + 1s. Den kollar först om cachedDateStr inte är lika med dagens datum (vilket indikerar att dagen har bytt) eller om cachedPrices inte är uppdaterad. Om så är fallet, anropar den fetchPrices() för att hämta dagens priser och uppdatera cachen, och i callback-funktionen för fetchPrices() kollar den om hämtningen lyckades (ok === true) och i så fall anropar postCurrentQuarter() för att posta det aktuella kvartets pris. Om cachen redan är giltig och uppdaterad, hoppar den över hämtningen och går direkt till att anropa postCurrentQuarter() för att posta priset.
function tick() {
if (cachedDateStr !== todayStr() || !cachedPrices) {
fetchPrices(function (ok) {
if (ok) postCurrentQuarter();
});
} else {
postCurrentQuarter();
}
}
// =====================================================================
// Schemaläggning på exakta klockslag (xx:00:01, xx:15:01, xx:30:01, xx:45:01)
// =====================================================================
// Funktionen msUntilNextQuarterPlusOne() räknar ut hur många millisekunder det är kvar tills nästa jämna kvart + 1 sekund. Den tar den aktuella tiden, beräknar hur många millisekunder som har gått sedan senaste timme, och jämför det med de fyra målen (00:01, 15:01, 30:01, 45:01) i millisekunder. Den returnerar skillnaden mellan det närmaste målet som är större än nuvarande tid och nuvarande tid i millisekunder. Om alla mål är mindre än nuvarande tid (dvs. om vi har passerat 45:01), returnerar den tiden tills nästa timmes 00:01.
function msUntilNextQuarterPlusOne() {
let now = new Date();
let mins = now.getMinutes();
let secs = now.getSeconds();
let ms = now.getMilliseconds();
let nowInHour = mins * 60000 + secs * 1000 + ms;
// Mål inom en timme: 00:01, 15:01, 30:01, 45:01
let targets = [
0 * 60000 + 1000,
15 * 60000 + 1000,
30 * 60000 + 1000,
45 * 60000 + 1000
];
for (let i = 0; i < targets.length; i++) {
if (targets[i] > nowInHour) return targets[i] - nowInHour;
}
// Inget mål kvar denna timme – sikta på nästa timmes :00:01
return (60 * 60000 + 1000) - nowInHour;
}
// Funktionen scheduleNextTick() anropar först msUntilNextQuarterPlusOne() och lagrar resultatet i variabeln wait. Den printar sedan ett meddelande om när nästa tick kommer att ske (i ms). Sedan använder den Timer.set() för att schemalägga en funktion som kommer att köras efter wait millisekunder. I den schemalagda funktionen, som körs på nästa jämna kvart + 1s, anropar den först scheduleNextTick() igen för att schemalägga nästa tick, och sedan anropar den tick() för att utföra själva jobbet med att posta priset. Genom att schemalägga nästa tick först, säkerställer den att kedjan av ticks fortsätter även om det skulle ske något fel i tick() som gör att den inte kör klart.
function scheduleNextTick() {
let wait = msUntilNextQuarterPlusOne();
print("Nästa tick om " + wait + " ms");
Timer.set(wait, false, function () {
// Schemalägg nästa först – då hänger kedjan kvar även om tick fallerar
scheduleNextTick();
tick();
});
}
// =====================================================================
// Uppstart: hämta priserna direkt (med liten fördröjning så WiFi hinner upp),
// men posta inte – posten ska bara ske på de aligned klockslagen.
// =====================================================================
Timer.set(10 * 1000, false, function () {
fetchPrices(function () {
scheduleNextTick();
});
}); Steg 2: Infoga Script
Steg 3: Hitta och kopiera din Token
Steg 4: Infoga token
Steg 5: Redigera scriptet
För att få scriptet konfigurerat för dina priser, krävs en liten modifiering.
Script beskrivning
PRICE_ZONE: SE1, SE2, SE3, SE4
Här skriver du vilken priszon du befinner dig i.
SE1 = Northern Sweden
SE2 = Northern Central Sweden
SE3 = Southern Central Sweden
SE4 = Southern Sweden
ELLVERANTOR_PÅSLAG:
Här kan du ange din elleverantörs vinstpåslag i kr/kwh.
ELCERTIFIKAT:
Här kan du ange kostnaderna för elcertifikat i kr/kwh.
OVERFORINGSAVGIFT: Här kan du ange ditt nätbolags överföringsavgifter i kr/kwh.
ELSKATT:
Här kan du ange din energiskatt/elskatt i kr/kwh.
ELBEREDSKAPSAVGIFT:
Detta är ofta ihopklumpat med elsäkerhetsavgift och natoövervakningsavgift som “Myndighetsavgifter”, använd en av dessa tre alternativ om det är så, du behöver inte dela upp det. Anges i kr/kwh.
ELSÄKERHETSAVGIFT:
Detta är ofta ihopklumpat med elberedskapsavgift och natoövervakningsavgift som “Myndighetsavgifter”, använd en av dessa tre alternativ om det är så, du behöver inte dela upp det. Anges i kr/kwh.
NATOÖVERVAKNINGSAVGIFT: Detta är ofta ihopklumpat med elberedskapsavgift och elsäkerhetsavgift som “Myndighetsavgifter”, använd en av dessa tre alternativ om det är så, du behöver inte dela upp det. Anges i kr/kwh.
VAT_RATE:
Förinställd på 25% moms men går att justera till 0.0 om man inte vill visa priser med moms, eller annat om man har det behovet. Anges i kr/kWh.
Användbara länkar för att kontrollera dina priser
Steg 6: Se till att scriptet körs
Steg 7: Slut på skriptinställning
När manuset har hämtat priset första gången ska du kunna se det uppdaterade priset under Energi -> Elpris.
Det är nödvändigt att uppdatera sidan genom att till exempel: för att trycka på F5
OBS! Om du kommer åt skriptet från enhetens webbgränssnitt via dess IP kommer du att kunna se en logg som visar vad som händer.
Viktig
- Om du trycker på Manual och Live igen genereras en ny token som sedan måste matas in i scriptet.
- Kom ihåg Tarifferna kan ändras flera gånger om året, så du måste justera detta manuellt.
- Detsamma gäller om skatter och överföringskostnader m.m. ändrats.
Om du har problem, fråga gärna i den svenska Facebookgruppen.







