Test Scripts
- Home
- Test Scripts
Elpriser: Aktuel pris, X billigste timer, Billigste sammenhængende blok
Forbehold:
– Min. Gen3, eller Pro enhed.
– Er teste med Firmware 1.6.2.
– Virker ikke med Firmwre: 1.7 beta
Script 1: Henter el priser hvert døgn og gemmer dem i KVS (Key value storage) på Shelly enheden.
Det eneste der skal ændres er tallene i Længdegrad (lat) og Breddegrad (long), så de passer til din lokation.
// ===== CONFIGURATION =====
let CONFIG = {
lat: 55.120019,
long: 12.054538,
run_seconds_after_midnight: 5 // How many seconds after 00:00 the script should run
};
// ==========================
function isDSTInEffect() {
try {
let now = new Date();
let year = now.getFullYear();
let march31 = new Date(year + "-03-31T00:00:00Z");
let lastSundayMarch = new Date(march31.getTime() - march31.getDay() * 24 * 3600 * 1000);
let oct31 = new Date(year + "-10-31T00:00:00Z");
let lastSundayOctober = new Date(oct31.getTime() - oct31.getDay() * 24 * 3600 * 1000);
return now >= lastSundayMarch && now < lastSundayOctober;
} catch (e) {
print("[FEJL] isDSTInEffect: " + e.message);
return false;
}
}
let LOCAL_OFFSET_HOURS = isDSTInEffect() ? 2 : 1;
print("[ELPRIS] Dansk sommertid aktiv? " + (LOCAL_OFFSET_HOURS === 2 ? "JA (+2)" : "NEJ (+1)"));
let supplierId = null;
let priceArea = null;
let nextRunTimer = null;
let logCountdownTimer = null;
let msToNextRun = 0;
function toISOStringNoMs(date) {
return date.toISOString().split(".")[0] + "Z";
}
function sortByHour(array) {
try {
for (let i = 0; i < array.length - 1; i++) {
for (let j = i + 1; j < array.length; j++) {
if (parseInt(array[i].hour) > parseInt(array[j].hour)) {
let temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
} catch (e) {
print("[FEJL] sortByHour: " + e.message);
}
}
function buildUrl() {
try {
let now = new Date();
let localMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate());
let fromDate = new Date(localMidnight.getTime() - 2 * 3600 * 1000); // 22:00 yesterday
let toDate = new Date(localMidnight.getTime() + 23 * 3600 * 1000); // 21:00 today
let fromStr = fromDate.toISOString().substring(0, 19) + "Z";
let toStr = toDate.toISOString().substring(0, 13) + ":45:00Z";
let url = "https://stromligning.dk/api/prices?from=" + fromStr +
"&to=" + toStr +
"&supplierId=" + supplierId +
"&priceArea=" + priceArea +
"&aggregation=1h" +
"&lean=true";
print("[ELPRIS] Genereret URL: " + url);
return url;
} catch (e) {
print("[FEJL] buildUrl: " + e.message);
return "";
}
}
function formatMs(ms) {
let sec = Math.floor(ms / 1000);
let h = Math.floor(sec / 3600);
let m = Math.floor((sec % 3600) / 60);
let s = sec % 60;
return h + "t " + m + "m " + s + "s";
}
function scheduleNextRun() {
try {
if (nextRunTimer) Timer.clear(nextRunTimer);
if (logCountdownTimer) Timer.clear(logCountdownTimer);
let now = new Date();
let msSinceMidnight = now.getHours() * 3600000 +
now.getMinutes() * 60000 +
now.getSeconds() * 1000 +
now.getMilliseconds();
let msToMidnight = 86400000 - msSinceMidnight;
let msDelay = msToMidnight + CONFIG.run_seconds_after_midnight * 1000;
msToNextRun = msDelay;
print("[ELPRIS] Næste opdatering planlagt om " + formatMs(msToNextRun));
nextRunTimer = Timer.set(msDelay, false, function () {
fetchSupplierAndThenLoadPrices();
scheduleNextRun();
});
logCountdownTimer = Timer.set(5 * 60 * 1000, true, function () {
msToNextRun -= 5 * 60 * 1000;
if (msToNextRun <= 0) {
Timer.clear(logCountdownTimer);
} else {
print("[ELPRIS] Næste opdatering om " + formatMs(msToNextRun));
}
});
} catch (e) {
print("[FEJL] scheduleNextRun: " + e.message);
}
}
function fetchSupplierAndThenLoadPrices() {
try {
let supplierUrl = "https://stromligning.dk/api/suppliers/find?lat=" + CONFIG.lat + "&long=" + CONFIG.long;
print("[ELPRIS] Henter supplier fra: " + supplierUrl);
Shelly.call("HTTP.GET", { url: supplierUrl }, function (res, errCode, errMsg) {
try {
if (errCode !== 0) {
print("[ELPRIS] Fejl ved supplier-API: " + errMsg);
return;
}
let suppliers = JSON.parse(res.body);
if (!suppliers || suppliers.length === 0) {
print("[ELPRIS] Ingen supplier-data modtaget.");
return;
}
supplierId = suppliers[0].id;
priceArea = suppliers[0].priceArea;
print("[ELPRIS] Fundet netselskab: " + supplierId + " (" + priceArea + ")");
loadPrices();
} catch (e) {
print("[FEJL] parse supplier response: " + e.message);
}
});
} catch (e) {
print("[FEJL] fetchSupplierAndThenLoadPrices: " + e.message);
}
}
function loadPrices() {
try {
let url = buildUrl();
if (!url) return;
Shelly.call("HTTP.GET", { url: url }, function(result, error_code, error_msg) {
try {
if (error_code !== 0) {
print("Fejl ved HTTP GET: " + error_msg);
return;
}
let data = JSON.parse(result.body);
print("Antal datapunkter modtaget: " + data.length);
let now = new Date();
let localNow = new Date(now.getTime() + LOCAL_OFFSET_HOURS * 3600 * 1000);
let todayLocalStr = localNow.toISOString().substring(0, 10);
let todayPrices = [];
for (let i = 0; i < data.length; i++) {
let utc = new Date(data[i].date);
let hourUTC = parseInt(data[i].date.substring(11, 13));
let hourLocal = (hourUTC + LOCAL_OFFSET_HOURS) % 24;
let localDate = new Date(utc.getTime() + LOCAL_OFFSET_HOURS * 3600 * 1000);
let localDateStr = localDate.toISOString().substring(0, 10);
if (localDateStr === todayLocalStr) {
todayPrices.push({
hour: ("0" + hourLocal).slice(-2),
price: parseFloat(data[i].price.toFixed(3))
});
}
}
if (todayPrices.length === 0) {
print("[ELPRIS] Ingen priser fundet for i dag (" + todayLocalStr + ")");
return;
}
sortByHour(todayPrices);
print("[ELPRIS] Priser for i dag (" + todayLocalStr + ") i lokal tid:");
let index = 0;
function showNextLine() {
if (index >= todayPrices.length) {
savePricesToKVS(todayPrices);
return;
}
let p = todayPrices[index];
print("Kl. " + p.hour + ":00 → " + p.price.toFixed(3) + " kr/kWh");
index++;
Timer.set(200, false, showNextLine);
}
showNextLine();
} catch (e) {
print("[FEJL] loadPrices: " + e.message);
}
});
} catch (e) {
print("[FEJL] loadPrices outer: " + e.message);
}
}
function savePricesToKVS(prices) {
let i = 0;
function writeNext() {
if (i >= prices.length) {
print("[KVS] Alle priser gemt i KVS.");
return;
}
let entry = prices[i];
let key = "price_" + entry.hour;
let value = entry.price.toFixed(3);
try {
Shelly.call("KVS.Set", { key: key, value: value }, function(res, err) {
if (err) {
print("[KVS] Fejl ved skrivning af " + key + ": " + JSON.stringify(err));
} else {
print("[KVS] Gemte " + key + " = " + value);
}
i++;
Timer.set(100, false, writeNext);
});
} catch (e) {
print("[FEJL] savePricesToKVS: " + e.message);
i++;
Timer.set(100, false, writeNext);
}
}
writeNext();
}
// 🚀 Start!
try {
fetchSupplierAndThenLoadPrices();
scheduleNextRun();
} catch (e) {
print("[FEJL] Hovedkørsel: " + e.message);
} Script 2: Henter priser fra KVS og kan styre relæ i 3 forskellige modes
Her er der lidt flere settings der skal ændres på, men de forklare meget godt sig selv 🙂
– mode: 1, // Vælg funktion: 1 = aktuel pris, 2 = billigste timer, 3 = billigste sammenhængende blok
– price_threshold: 1.20, // Bruges i mode 1 – maks pris for at tænde
– cheapest_hours: 13, // Bruges i mode 2 og 3 – antal timer/blok
– relay_id: 0, // ID på relæ der skal styres (brug fx 99 hvis intet relæ skal styres)
– invert: false, // true = vend logikken (TÆND bliver SLUK)
– run_seconds_after_hour: 10, // Forsinkelse i sek. efter timeskift
– debug: false // true = vis alle priser ved opstart
// =====================================================
// ⚡ SIMPEL GUIDE TIL ELPRIS-SCRIPTET
// =====================================================
//
// Dette script tænder/slukker et relæ baseret på elpriser.
// Det opdateres hver time og bruger én af tre funktioner (modes).
//
// ➕ VIGTIGT: Scriptet henter priser fra KVS!
// Det kræver derfor, at et andet script
// allerede har gemt priserne i KVS som:
// "price_00", "price_01", ..., "price_23"
//
// -----------------------------------------------------
// 🔧 KONFIGURATION (øverst i scriptet)
//
// mode: 1, // Vælg funktion: 1 = aktuel pris, 2 = billigste timer, 3 = billigste sammenhængende blok
// price_threshold: 1.20, // Bruges i mode 1 – maks pris for at tænde
// cheapest_hours: 13, // Bruges i mode 2 og 3 – antal timer/blok
// relay_id: 0, // ID på relæ der skal styres (brug fx 99 hvis intet relæ skal styres)
// invert: false, // true = vend logikken (TÆND bliver SLUK)
// run_seconds_after_hour: 10, // Forsinkelse i sek. efter timeskift
// debug: false // true = vis alle priser ved opstart
//
// -----------------------------------------------------
// 🔁 MODES
//
// ▶️ MODE 1 – AKTUEL PRIS
// - Tænder hvis prisen er under price_threshold
//
// ▶️ MODE 2 – X BILLIGSTE TIMER (SPREDT)
// - Tænder i de billigste timer (ikke nødvendigvis i træk)
//
// ▶️ MODE 3 – X BILLIGSTE BLOK (SAMMENHÆNGENDE)
// - Tænder i den billigste sammenhængende periode
//
// -----------------------------------------------------
// 📊 VIRTUELLE KOMPONENTER
//
// Boolean 200:
// - Mode 1: Sand hvis pris < threshold
// - Mode 2: Sand hvis aktuel time er blandt de billigste
// - Mode 3: Sand hvis aktuel time er i billigste blok
//
// Number 200:
// - Viser aktuel pris (afrundet til 2 decimaler)
// → Bruges i scener: fx "hvis pris < 1.50 → sluk tørretumbler"
//
// Number 201:
// - Viser placering af aktuel time (1 = billigst)
// → Bruges i scener: fx "hvis værdi < 6 → tænd enhed"
//
// Number 202:
// - Mode 3: Viser gennemsnitspris for billigste blok
//
// Text 200:
// - Mode 2: Viser billigste timer (fx "01, 05, 14")
// - Mode 3: Viser timer i billigste blok (fx "04, 05, 06, ...")
//
// =====================================================
// ====== KONFIGURATION ======
let CONFIG = {
mode: 2, // 1 = aktuel pris, 2 = X billigste timer, 3 = X billigste sammenhængende timer
price_threshold: 1.20, // Bruges i mode 1
cheapest_hours: 13, // Bruges i mode 2 og 3
relay_id: 0, // ID på relæ der skal styres
invert: false, // Inverter logik i ALLE modes
run_seconds_after_hour: 10, // Hvor mange sekunder efter hver hele time checket skal køre
debug: false // Vis KVS-priser ved opstart
};
function pad(n) {
return (n < 10 ? "0" + n : n);
}
function sortByHour(prices) {
for (let i = 0; i < prices.length - 1; i++) {
for (let j = 0; j < prices.length - i - 1; j++) {
if (prices[j].hour > prices[j + 1].hour) {
let temp = prices[j];
prices[j] = prices[j + 1];
prices[j + 1] = temp;
}
}
}
}
function sortPricesAscending(prices) {
for (let i = 0; i < prices.length - 1; i++) {
for (let j = 0; j < prices.length - i - 1; j++) {
if (prices[j].price > prices[j + 1].price) {
let temp = prices[j];
prices[j] = prices[j + 1];
prices[j + 1] = temp;
}
}
}
}
function readAllPricesFromKVS() {
if (!CONFIG.debug) return;
let hour = 0;
function readNext() {
if (hour >= 24) return;
let key = "price_" + pad(hour);
Shelly.call("KVS.Get", { key: key }, function (result, error) {
if (!error && result && result.value !== undefined) {
print(key + " =", result.value);
}
});
hour++;
Timer.set(200, false, readNext);
}
readNext();
}
function fetchPricesAndRunMode(callback) {
let prices = [];
let hour = 0;
function readNext() {
if (hour >= 24) {
if (prices.length !== 24) {
print("❌ FEJL: Forventede 24 priser, men fik", prices.length, ". Afbryder.");
return;
}
sortByHour(prices);
print("Laver udregning...");
Timer.set(100, false, function () {
try {
callback(prices);
} catch (e) {
print("Fejl i callback:", e.message);
}
});
return;
}
let key = "price_" + pad(hour);
Shelly.call("KVS.Get", { key: key }, function (result, error) {
if (!error && result && result.value !== undefined) {
prices.push({ hour: hour, price: parseFloat(result.value) });
}
hour++;
Timer.set(200, false, readNext);
});
}
readNext();
}
function checkCurrentPriceMode1() {
let hour = new Date().getHours();
let key = "price_" + pad(hour);
Shelly.call("KVS.Get", { key: key }, function (result, error) {
if (error || !result || result.value === undefined) {
print("Fejl ved hentning af pris for", key);
return;
}
let price = parseFloat(result.value);
let roundedPrice = Math.round(price * 100) / 100;
print("Aktuel pris kl", pad(hour) + ":00 =", roundedPrice, "kr");
let shouldTurnOn = !CONFIG.invert ? price < CONFIG.price_threshold : price >= CONFIG.price_threshold;
print("Logik (mode 1, invert=" + CONFIG.invert + "): " + (shouldTurnOn ? "TÆND" : "SLUK"));
Shelly.call("Switch.Set", { id: CONFIG.relay_id, on: shouldTurnOn });
Shelly.call("Number.Set", { id: 200, value: roundedPrice });
Shelly.call("Boolean.Set", { id: 200, value: shouldTurnOn });
updatePriceRanking();
});
}
function checkCheapestHoursMode2(prices) {
sortPricesAscending(prices);
let selected = [];
for (let i = 0; i < CONFIG.cheapest_hours && i < prices.length; i++) {
selected.push(prices[i]);
}
sortByHour(selected);
let currentHour = new Date().getHours();
let match = false;
for (let i = 0; i < selected.length; i++) {
if (selected[i].hour === currentHour) {
match = true;
break;
}
}
let shouldTurnOn = CONFIG.invert ? !match : match;
let hourList = selected.map(function (item) {
return item ? pad(item.hour) : "?";
}).join(", ");
let currentPrice = null;
for (let i = 0; i < prices.length; i++) {
if (prices[i].hour === currentHour) {
currentPrice = prices[i].price;
break;
}
}
print("Billigste timer:", hourList);
print("Aktuel time:", pad(currentHour), "→", shouldTurnOn ? "TÆND" : "SLUK");
if (currentPrice !== null) {
let roundedPrice = Math.round(currentPrice * 100) / 100;
print("Aktuel pris:", roundedPrice, "kr");
}
Shelly.call("Switch.Set", { id: CONFIG.relay_id, on: shouldTurnOn });
Timer.set(100, false, function () {
Shelly.call("Text.Set", { id: 200, value: hourList });
Timer.set(100, false, function () {
Shelly.call("Boolean.Set", { id: 200, value: shouldTurnOn });
Timer.set(100, false, function () {
if (currentPrice !== null) {
let roundedPrice = Math.round(currentPrice * 100) / 100;
Shelly.call("Number.Set", { id: 200, value: roundedPrice });
}
Timer.set(100, false, updatePriceRanking);
});
});
});
}
function checkCheapestSequenceMode3(prices) {
let minSum = 99999;
let startIndex = 0;
for (let i = 0; i <= 24 - CONFIG.cheapest_hours; i++) {
let sum = 0;
for (let j = 0; j < CONFIG.cheapest_hours; j++) {
sum += prices[i + j].price;
}
if (sum < minSum) {
minSum = sum;
startIndex = i;
}
}
let selected = [];
for (let i = 0; i < CONFIG.cheapest_hours; i++) {
selected.push(prices[startIndex + i]);
}
let currentHour = new Date().getHours();
let match = false;
let totalPrice = 0;
let hourList = "";
for (let i = 0; i < selected.length; i++) {
let h = selected[i].hour;
hourList += pad(h) + (i < selected.length - 1 ? ", " : "");
totalPrice += selected[i].price;
if (h === currentHour) match = true;
}
let averagePrice = Math.round(totalPrice * 100 / CONFIG.cheapest_hours) / 100;
let shouldTurnOn = CONFIG.invert ? !match : match;
let currentPrice = null;
for (let i = 0; i < prices.length; i++) {
if (prices[i].hour === currentHour) {
currentPrice = prices[i].price;
break;
}
}
print("Sammenhængende billigste blok:", hourList);
print("Gennemsnitlig pris for blokken:", averagePrice, "kr");
print("Aktuel time:", pad(currentHour), "→", shouldTurnOn ? "TÆND" : "SLUK");
if (currentPrice !== null) {
let roundedPrice = Math.round(currentPrice * 100) / 100;
print("Aktuel pris:", roundedPrice, "kr");
}
Shelly.call("Switch.Set", { id: CONFIG.relay_id, on: shouldTurnOn });
Timer.set(100, false, function () {
Shelly.call("Text.Set", { id: 200, value: hourList });
Timer.set(100, false, function () {
Shelly.call("Number.Set", { id: 202, value: averagePrice });
Timer.set(100, false, function () {
Shelly.call("Boolean.Set", { id: 200, value: shouldTurnOn });
Timer.set(100, false, function () {
if (currentPrice !== null) {
let roundedPrice = Math.round(currentPrice * 100) / 100;
Shelly.call("Number.Set", { id: 200, value: roundedPrice });
}
Timer.set(100, false, updatePriceRanking);
});
});
});
});
}
// ➕ NY FUNKTION: Finder placering af aktuel time (1–24)
function updatePriceRanking() {
let hour = 0;
let prices = [];
function readNext() {
if (hour >= 24) {
if (prices.length !== 24) {
print("❌ Kunne ikke opdatere prisplacering – mangler data");
return;
}
let sorted = prices.slice();
sortPricesAscending(sorted);
let currentHour = new Date().getHours();
let placement = 0;
for (let i = 0; i < sorted.length; i++) {
if (sorted[i].hour === currentHour) {
placement = i + 1;
break;
}
}
if (placement > 0) {
print("📊 Aktuel time er nr.", placement, "billigst");
Shelly.call("Number.Set", { id: 201, value: placement });
} else {
print("❌ Aktuel time ikke fundet i sorteret liste");
}
return;
}
let key = "price_" + pad(hour);
Shelly.call("KVS.Get", { key: key }, function (result, error) {
if (!error && result && result.value !== undefined) {
prices.push({ hour: hour, price: parseFloat(result.value) });
}
hour++;
Timer.set(50, false, readNext);
});
}
readNext();
}
function runModeCheck() {
try {
if (CONFIG.mode === 1) {
checkCurrentPriceMode1();
} else if (CONFIG.mode === 2) {
fetchPricesAndRunMode(checkCheapestHoursMode2);
} else if (CONFIG.mode === 3) {
fetchPricesAndRunMode(checkCheapestSequenceMode3);
} else {
print("Ukendt mode:", CONFIG.mode);
}
} catch (e) {
print("Fejl i runModeCheck:", e.message);
}
}
function startCountdownLogger() {
Timer.set(30000, true, function () {
let now = new Date();
let msToNextHour = (60 - now.getMinutes()) * 60 * 1000 - now.getSeconds() * 1000;
let totalSeconds = Math.floor(msToNextHour / 1000);
let minutes = Math.floor(totalSeconds / 60);
let seconds = totalSeconds % 60;
print("⏳ Tid til næste prischeck: " + pad(minutes) + ":" + pad(seconds));
});
}
function scheduleNextHourlyCheck() {
let now = new Date();
let msPastHour = (now.getMinutes() * 60 + now.getSeconds()) * 1000 + now.getMilliseconds();
let msToNextHour = 3600000 - msPastHour + CONFIG.run_seconds_after_hour * 1000;
let totalSec = Math.floor(msToNextHour / 1000);
let min = Math.floor(totalSec / 60);
let sec = totalSec % 60;
print("🕒 Planlægger næste check om " + pad(min) + ":" + pad(sec));
Timer.set(msToNextHour, false, function () {
runModeCheck();
scheduleNextHourlyCheck();
});
}
// Start
readAllPricesFromKVS();
runModeCheck();
startCountdownLogger();
scheduleNextHourlyCheck();