Shelly Logo Squared

Script Shelly scripts

What your power costs on a time-of-use tariff, worked out daily

Sorts every kilowatt-hour your Shelly meter counts into peak, shoulder and off-peak, and adds up what the day cost. Sends the total to your phone each night if you want it.

Updated v1.0

  • Gen3
  • Gen4
  • Pro
electricity-cost-time-of-use.js · 157 lines Download .js
// Electricity cost tracker: adds up what your power costs on a time-of-use tariff.// Needs a Shelly energy meter (EM Mini Gen4, EM Gen3 or Pro EM-50). let CONFIG = {  peakRate: 45.0,  shoulderRate: 30.0,  offPeakRate: 20.0,  peakHours: "15:00-21:00",  offPeakHours: "22:00-07:00",  peakOnWeekends: true,  dailySupply: 110.0,  ntfyTopic: "",  meterId: 0,}; // Read the meter once a minute: often enough that each minute's energy lands// in the right band, and the meter's own counter keeps the total exact.let READ_EVERY_SEC = 60;// Save the day's totals every 15 minutes, so a restart loses at most that.let SAVE_EVERY_MIN = 15;let KVS_KEY = "tou-cost"; // kWh used today in each band, and the meter reading they were counted from.let day = { date: -1, lastWh: null, peak: 0, shoulder: 0, offPeak: 0 };let minutes = 0;let lastHour = -1;let waitingForClock = false; function toMinutes(hhmm) {  let p = hhmm.split(":");  return Number(p[0]) * 60 + Number(p[1]);} // "15:00-21:00" or "07:00-09:00,17:00-20:00". A range can run past midnight.function inHours(hours, m) {  if (hours === "") return false;  let ranges = hours.split(",");  for (let i = 0; i < ranges.length; i++) {    let p = ranges[i].split("-");    let a = toMinutes(p[0]);    let b = toMinutes(p[1]);    if (a <= b && m >= a && m < b) return true;    if (a > b && (m >= a || m < b)) return true;  }  return false;} // Local date (days since 1970) and minute of the day, or null before the// clock has synced.function localNow() {  let s = Shelly.getComponentStatus("sys");  if (s === null || typeof s.unixtime !== "number") return null;  let t = s.unixtime + (s.utc_offset || 0);  return { date: Math.floor(t / 86400), min: Math.floor((t % 86400) / 60) };} function bandAt(now) {  // 1 January 1970 was a Thursday, so this gives 0 for Sunday, 6 for Saturday.  let weekday = (now.date + 4) % 7;  let weekend = weekday === 0 || weekday === 6;  if (inHours(CONFIG.peakHours, now.min) && (CONFIG.peakOnWeekends || !weekend)) return "peak";  if (inHours(CONFIG.offPeakHours, now.min)) return "offPeak";  return "shoulder";} function dollars(cents) {  return "$" + (cents / 100).toFixed(2);} function summary(d) {  let cents = d.peak * CONFIG.peakRate + d.shoulder * CONFIG.shoulderRate +    d.offPeak * CONFIG.offPeakRate + CONFIG.dailySupply;  return "peak " + d.peak.toFixed(2) + " kWh, shoulder " + d.shoulder.toFixed(2) +    " kWh, off-peak " + d.offPeak.toFixed(2) + " kWh. " + dollars(cents) +    " including supply.";} function onSent(res, err) {  if (err !== 0 || res.code !== 200) print("Couldn't send the daily summary to ntfy.");} function closeDay() {  let msg = "Electricity yesterday: " + summary(day);  print(msg);  if (CONFIG.ntfyTopic === "") return;  Shelly.call("HTTP.Request", {    method: "POST",    url: "https://ntfy.sh/" + CONFIG.ntfyTopic,    body: msg,    headers: { "Content-Type": "text/plain" },    timeout: 15,  }, onSent);} function save() {  Shelly.call("KVS.Set", { key: KVS_KEY, value: JSON.stringify(day) });} function readWh() {  let d = Shelly.getComponentStatus("em1data", CONFIG.meterId);  if (d === null || typeof d.total_act_energy !== "number") return null;  return d.total_act_energy;} function tick() {  let now = localNow();  let wh = readWh();  // Without the clock the script can't tell which band it's in. It leaves the  // reading where it was, so the energy is counted once the clock syncs.  if (now === null || wh === null) {    if (!waitingForClock) print("Waiting for the clock to sync before counting.");    waitingForClock = true;    return;  }  waitingForClock = false;  if (day.lastWh !== null && day.date !== -1) {    let used = wh - day.lastWh;    // A meter reset makes the reading go backwards: start again from it.    if (used > 0) day[bandAt(now)] += used / 1000;  }  day.lastWh = wh;  if (day.date !== now.date) {    if (day.date !== -1) closeDay();    day = { date: now.date, lastWh: wh, peak: 0, shoulder: 0, offPeak: 0 };    save();  }  minutes++;  if (minutes % SAVE_EVERY_MIN === 0) save();  let hour = Math.floor(now.min / 60);  if (hour !== lastHour) {    lastHour = hour;    print("Today so far: " + summary(day));  }} function start() {  Timer.set(READ_EVERY_SEC * 1000, true, tick);  tick();} function onLoaded(res, err) {  if (err === 0 && res && typeof res.value === "string") {    try {      let saved = JSON.parse(res.value);      if (typeof saved.date === "number") {        day = saved;        print("Picked up the totals saved before the restart.");      }    } catch (e) {      print("Saved totals were unreadable. Starting from zero.");    }  }  start();} print("Electricity cost tracker started.");Shelly.call("KVS.Get", { key: KVS_KEY }, onLoaded);

What it does

Once a minute the script reads your meter's energy counter and adds what was used since the last reading to the rate that applies right then: peak, shoulder or off-peak. At the start of each hour it prints the day so far, in kilowatt-hours and dollars, with the daily supply charge included. Just after midnight it prints the whole day and, if you've set a topic, sends it to your phone through ntfy.

It saves the day's totals on the Shelly every 15 minutes, so a restart loses at most a quarter of an hour, and anything used during the restart goes into the rate it comes back in. Until the Shelly's clock has synced it counts nothing, then picks up the energy from the meter, so none goes missing. The total is worked out from your rates alone. Discounts, solar credits and demand charges aren't in it, so expect it to differ a little from the bill.

Install it on your Shelly

View the full install guide

Before you install

  1. Check the meter and its clock

    Once the meter is installed, check it shows power in the Shelly app and that the device's time zone is your own. The script reads the device's clock to work out which rate applies.

  2. Subscribe in ntfy (optional)

    Install the ntfy app and subscribe to a topic name nobody would guess, such as smith-power-4821. Anyone who knows the name can read what's sent to it.

Install the script

Install it on the meter.

Shelly Smart Control

  1. Open the device and tap Scripts

    In Shelly Smart Control, open the room, then the device. Tap Scripts, the { } icon in the menu down the left.

  2. Tap Create new script

    If the device has no scripts yet, the screen says No scripts created.

  3. Name it, paste the code, then tap Save

    Name it electricity-cost-time-of-use. Tap in the code area, paste the script you copied above, then tap Save (the disk icon at the top right).

  4. Tap the play button

    The play button lights up once the script is saved. Tap it and a message says the script has been started.

  5. Close the editor and turn on Run on startup

    Back on the Scripts screen, turn on Run on startup next to your script, so it starts again after a power cut or a firmware update.

Direct access via IP

  1. Open the device's web page and click Scripts

    Find the IP address in the Shelly app under Device settings, then Device information, and type it into a browser on the same Wi-Fi. Click Scripts in the menu on the left.

  2. Click Create script

    If the device has no scripts yet, the list says so.

  3. Name it, paste the code, then click Save

    Name it electricity-cost-time-of-use. Click in the code area, paste the script you copied above, then click Save (the disk icon above the code).

  4. Click Run and check the console

    Run appears once the script is saved. The console under the code then prints what the script is doing.

  5. Turn on Run on startup

    Click Scripts again and turn on Run on startup next to your script, so it starts again after a power cut or a firmware update.

After you install

  1. Check the console

    On the meter's web page, the console prints the day so far at the start of every hour, and the full day just after midnight.

Changelog

v1.0 First release.

Still need a hand?

We answer most messages within one business day. If your question is about an existing order, have your order number ready. It speeds things up.

Contact us