Espresso reverse timer and notification with power plug

ESPRESSO machine warmup timer in openHAB (ECMAScript rule + custom widget + push notification)

Hi folks,
I wanted a simple “espresso warmup” UI for my Lelit Mara X already controlled via a z-wave smart plug in openHAB:

  • Toggle machine ON/OFF from MainUI / Android app

  • When switched ON: start a reverse warmup timer (24:00 → 00:00)

  • Display the remaining time as mm:ss

  • Send a push notification when the machine is ready

  • Allow switching OFF anytime (plan changes)

This post documents the working solution using a JS Scripting rule (ECMAScript 262 Edition 11) plus a custom MainUI widget.


Motivation

The Mara X needs ~24 minutes to reach stable temperature. I already had the machine on a plug in openHAB, but I wanted a “one glance” widget that:

  1. shows ON/OFF,

  2. shows the countdown live,

  3. displays power consumption to check function

  4. alerts me when it’s ready.


Solution path (what worked reliably)

  1. Create a Number item that stores remaining seconds (e.g. 1440 for 24 minutes).

  2. Create a JS rule that:

    • starts/restarts the countdown whenever the espresso plug is switched ON

    • cancels/reset when switched OFF

    • decrements every second

    • sends a notification when reaching 0 from counting

  3. Create a custom widget that:

    • shows a toggle for the plug

    • formats the seconds item as mm:ss

    • shows power consumption (Watts) in a second line

Note: for the Z-Wave switch channel link I kept the default profile (no Script Profile). The automation belongs in the rule, not the channel link.


Required Items

  • Your existing plug switch item (example): ZP55PCPower_Switch

  • A “remaining seconds” Number item: Lelit_RS

  • An optional status switch: Lelit_WarmupActive

(Items can be created in the UI: Settings → Items → Add Item.)


Required Rule (ECMAScript 262 Edition 11)

Trigger

  • When Item state changedZP55PCPower_Switch
    (Works great for ON/OFF. If you want “restart timer even if ON is pressed again while already ON”, use “Item received command”.)

Script (paste into the Rule action)

This version:

  • starts at 24 minutes

  • counts down Lelit_RS

  • aborts instantly if you switch OFF

  • sends push notification when reaching READY from counting

// Lelit Warmup – ECMAScript 262 Edition 11
// Trigger: ZP55PCPower_Switch toggled (state changed or command received)

const PLUG_ITEM   = "ZP55PCPower_Switch";
const REMAIN_ITEM = "Lelit_RS";
const ACTIVE_ITEM = "Lelit_WarmupActive";

const WARMUP_SECONDS   = 24 * 60; // 24 minutes
const NOTIFY_RECIPIENT = "ohuser@mydomain.com";

const TIMER_KEY    = "Lelit_WarmupInterval";
const NOTIFIED_KEY = "Lelit_Notified";

function stopTimer(resetRemaining) {
  const t = cache.private.get(TIMER_KEY);
  if (t) {
    clearInterval(t);
    cache.private.remove(TIMER_KEY);
  }
  items.getItem(ACTIVE_ITEM).postUpdate("OFF");
  cache.private.put(NOTIFIED_KEY, "0");
  if (resetRemaining) items.getItem(REMAIN_ITEM).postUpdate(0);
}

function startTimer() {
  // restart every time we start
  stopTimer(false);

  items.getItem(REMAIN_ITEM).postUpdate(WARMUP_SECONDS);
  items.getItem(ACTIVE_ITEM).postUpdate("ON");
  cache.private.put(NOTIFIED_KEY, "0");

  const tick = () => {
    // If user switched off anytime -> abort
    if (String(items.getItem(PLUG_ITEM).state) !== "ON") {
      stopTimer(true);
      return;
    }

    let remaining = parseInt(String(items.getItem(REMAIN_ITEM).state), 10);
    if (!Number.isFinite(remaining)) remaining = 0;

    const prev = remaining;
    remaining = Math.max(0, remaining - 1);
    items.getItem(REMAIN_ITEM).postUpdate(remaining);

    if (remaining === 0) {
      // Notify only if we reached 0 from counting (>0) and not yet notified
      const notified = String(cache.private.get(NOTIFIED_KEY) || "0") === "1";
      if (prev > 0 && !notified) {
        actions.NotificationAction.sendNotification(
          NOTIFY_RECIPIENT,
          "Lelit Mara X ist aufgeheizt ☕"
        );
        cache.private.put(NOTIFIED_KEY, "1");
      }
      stopTimer(false);
    }
  };

  cache.private.put(TIMER_KEY, setInterval(tick, 1000));
}

// ---- event handling (supports different trigger payloads) ----
(function (event) {
  // if run manually, just (re)start timer
  if (typeof event === "undefined" || !event) {
    startTimer();
    return;
  }

  // only act on plug item
  if (event.itemName && event.itemName !== PLUG_ITEM) return;

  // different triggers expose different fields
  let newState =
    (event.newState !== undefined ? event.newState : undefined) ??
    (event.itemState !== undefined ? event.itemState : undefined) ??
    (event.receivedState !== undefined ? event.receivedState : undefined) ??
    (event.receivedCommand !== undefined ? event.receivedCommand : undefined);

  newState = String(newState);

  if (newState === "ON") {
    startTimer();          // restart countdown every ON trigger
  } else if (newState === "OFF") {
    stopTimer(true);       // cancel anytime + reset
  }
})(event);

Push notification note

This uses openHAB Cloud notifications (sendNotification). It will deliver to the openHAB app for the user account matching NOTIFY_RECIPIENT (myopenHAB email).


Custom Widget (toggle + remaining time + watts)

Create a personal widget in MainUI:

  • Developer Tools → Widgets → Create Widget

  • UID: lelit-warmup

  • Paste YAML:

uid: lelit-warmup
component: f7-card
config:
  title: "Lelit Mara X"
  style:
    padding: 12px

slots:
  default:
    - component: f7-row
      config:
        class:
          - align-items-start
      slots:
        default:
          # Left: toggle
          - component: f7-col
            config:
              style:
                display: flex
                align-items: center
                height: 100%
            slots:
              default:
                - component: oh-toggle
                  config:
                    item: ZP55PCPower_Switch

          # Right: two-line status
          - component: f7-col
            config:
              class:
                - text-align-right
            slots:
              default:
                # Line 1: remaining time in mm:ss (OFF/READY fallback)
                - component: Label
                  config:
                    style:
                      font-size: 22px
                      font-family: monospace
                      line-height: 1.1
                    text: "=(items['ZP55PCPower_Switch'].state !== 'ON') ? 'OFF' : ((Number.parseInt(items['Lelit_RS'].state,10) > 0) ? (('0' + Math.floor(Number.parseInt(items['Lelit_RS'].state,10)/60)).slice(-2) + ':' + ('0' + (Number.parseInt(items['Lelit_RS'].state,10)%60)).slice(-2)) : 'READY')"

                # Line 2: power consumption (Watts)
                - component: Label
                  config:
                    style:
                      font-size: 14px
                      opacity: 0.8
                      margin-top: 4px
                    text: "=((items['ZP55PCPower_Electricmeterwatts'].state === 'NULL' || items['ZP55PCPower_Electricmeterwatts'].state === 'UNDEF') ? '-- W' : (items['ZP55PCPower_Electricmeterwatts'].state + ' W'))"

If your watt item already includes the unit (e.g. 123 W as a QuantityType), simply remove the extra ' W' in the widget line.


Result

  • Toggle ON → timer starts at 24:00 and counts down

  • Toggle OFF at any time → timer cancels/reset

  • When reaching 0 from counting → push notification “ready”

  • Widget shows remaining time mm:ss + current power draw

Hope this helps anyone building a coffee dashboard in openHAB :hot_beverage:

1 Like