Skip to content

late4marshmellow/MMM-EnergyPulse

Repository files navigation

MMM-EnergyPulse

MMM-EnergyPulse screenshot

MMM-EnergyPulse is a MagicMirror module that continuously tracks power readings (Watts) and renders the last 24 hours as paired bars per hour:

  • Consumed energy (positive Watts)
  • Produced energy (negative Watts, shown as absolute value)

It also tracks cost of consumed energy and shows:

  • Current hour cost (live)
  • Last 24h import cost
  • Last 24h net cost (import minus export credit)
  • Current import rate, support discount, and network tariff component

Features (MVP)

  • MQTT ingestion pattern similar to MMM-MultiGauge
  • Homey-friendly topic support (plain or JSON payload)
  • Rolling 24h chart with current hour at far right
  • Brighter current hour bars, faded historical bars
  • Persistent storage to JSON file (optional)
  • Dynamic hourly spot prices from Nord Pool (same source family as MMM-EUElectricityPrice)
  • Smart price polling: startup fetch, frequent retries only while stale, then daily refresh around Nord Pool publish time (Oslo)
  • Configurable pricing pipeline: VAT, surcharges, network tariff rules, pricePolicy (Norgespris-style fixed/cap), subsidy (strømstøtte-style), export credit

Installation

cd ~/MagicMirror/modules/MMM-EnergyPulse
npm install

Example config

Add this to config/config.js:

{
  module: "MMM-EnergyPulse",
  position: "bottom_center",
  config: {
    title: "Energy Pulse",
    hoursToShow: 24,
    showHourEvery: 2,
    fixedMaxWh: null,       // e.g. 5000 to lock Y scale to 5 kWh per hour
    liveUpdateIntervalMs: 5000,
    width: null,            // e.g. "900px"
    height: null,           // e.g. "260px"
    posRight: null,         // e.g. "80px" (translateX)
    posDown: null,          // e.g. "10px" (translateY)
    chartJustify: "center", // "left" | "center" | "right" | "between"
    showNetworkNow: true,
    showSupportNow: true,
    showSpotNow: true,
    showUsageNow: true,
    showGraphNowIndicator: true,

    mqtt: {
      url: "mqtt://192.168.1.50:1883",
      username: "",
      password: "",
      clientId: "mmm-energypulse",
      qos: 0,
      insecureTLS: false
    },

    source: {
      topic: "homey/devices/YOUR_DEVICE_ID/capabilities/measure_power/value",
      // Uses exactly what you configure (no Homey-specific normalization).
      // Can be a single topic string or an array of topics.
      parser: "plain", // "plain" or "json"
      valuePath: "value",
      multiplier: 1,
      offset: 0
    },

    cost: {
      currency: "NOK",
      importPricePerKWh: 1.35, // fallback if dynamic pricing has no hour price
      exportCreditPerKWh: 0.20, // fallback if dynamic pricing has no hour price
      decimals: 2,
      showNet: true
    },

    pricing: {
      enabled: true,
      dataSource: "NO1",      // NO1, NO2, NO3, NO4, NO5, SE1..SE4, DK1, DK2, FI, etc
      currency: "NOK",
      hourOffset: null,         // null = local timezone offset
      updateIntervalMinutes: 15,
      publishHourOslo: 13,
      publishMinuteOslo: 0,
      publishWindowMinutes: 30,
      maxFreshAgeHours: 12,
      inputUnit: "auto",       // "major" | "subunit" | "auto"
      autoSubunitThreshold: 10, // used only when inputUnit is "auto"

      adjustments: {
        energyMultiplier: 1.0,  // e.g. supplier multiplier
        vatPercent: 25,
        supplierSurchargePerKWh: 0.00,
        otherSurchargePerKWh: 0.00,
        networkTariffApi: {
          baseUrl: "",
          headers: {},
          updateIntervalSeconds: 60,
          timeoutMs: 8000,
          retries: 1
        },
        networkTariffSources: {},
        networkTariffRules: [
          {from: "06:00", to: "22:00", addPerKWh: 0.00},
          {from: "22:00", to: "06:00", addPerKWh: 0.00}
        ]
      },

      pricePolicy: {
        enabled: false,
        fixedImportPerKWh: 0.50,
        maxImportPerKWh: 0.50
      },

      subsidy: {
        enabled: true,
        thresholdPerKWh: 0.9375,
        percent: 0.90
      },

      featureToggle: {
        preventSimultaneousPolicyAndSubsidy: true,
        priority: "pricePolicy", // "pricePolicy" or "subsidy"
        logWarning: true
      },

      exportCredit: {
        mode: "spot",          // "spot" | "spot-minus-surcharge" | "fixed"
        multiplier: 1.0,
        offsetPerKWh: 0.00,
        fixedPerKWh: 0.00,
        minPerKWh: null,
        maxPerKWh: null
      }
    },

    pricingProfile: {
      enabled: false,
      name: "NO1.json",
      overrides: {}
    },

    storage: {
      enabled: true,
      fileName: "energy-history.json",
      retainHours: 72
    },

    maxIntegrationGapSeconds: 300,
    verbose: false
  }
}

Price fetch cadence

By default, the module does this:

  • Fetches prices immediately on startup.
  • Runs scheduler checks every pricing.updateIntervalMinutes (default 15 min).
  • If fetched prices are older than today (Oslo day), it keeps retrying on that interval.
  • Once prices are current, it fetches once in the Nord Pool publish window (publishHourOslo / publishMinuteOslo + publishWindowMinutes).
  • Also performs a safety refresh if the last successful fetch age exceeds maxFreshAgeHours.

Manual tariff rules with API values

adjustments.networkTariffRules[].addPerKWh supports:

  • number: fixed tariff value
  • numeric string: fixed tariff value

This lets you keep manual day/night time windows while sourcing values from API endpoints.

Recommended pattern (no duplication):

  • Define shared API settings in adjustments.networkTariffApi
  • Define sources once in adjustments.networkTariffSources
  • Keep time windows only in adjustments.networkTariffRules
  • In rules, reference a source by name: "source:day" / "source:night"

Example:

adjustments: {
  networkTariffApi: {
    baseUrl: "http://192.168.10.35",
    headers: { Authorization: "Bearer YOUR_HOMEY_TOKEN" },
    updateIntervalSeconds: 60,
    timeoutMs: 8000,
    retries: 1
  },
  networkTariffSources: {
    day: { apiPath: "/api/manager/logic/variable/<DAY_ID>", parser: "json", valuePath: "value", multiplier: 100 },
    night: { apiPath: "/api/manager/logic/variable/<NIGHT_ID>", parser: "json", valuePath: "value", multiplier: 100 }
  },
  networkTariffRules: [
    { from: "06:00", to: "22:00", addPerKWh: "source:day" },
    { from: "22:00", to: "06:00", addPerKWh: "source:night" }
  ]
}

Tariff source value priority is:

  1. Latest API value (last successful poll)
  2. Saved persisted value (from last valid API value)
  3. Fallback value in networkTariffSources

If saved value is 0, fallback value is used on startup until a new API value arrives.

Graph sizing and positioning

You can tune layout similarly to MMM-EUElectricityPrice:

  • width: module width (for example "900px")
  • height: module height (for example "260px")
  • posRight: horizontal translate offset (for example "50px")
  • posDown: vertical translate offset (for example "10px")
  • chartJustify: horizontal bar alignment inside width: "left", "center", "right", "between"

Summary tag stacking and visibility

Summary pills are grouped into horizontal stacks (rows):

  • Costs row
  • Prices row
  • Usage row

Visibility options:

  • showNetworkNow: show/hide Network now in prices row
  • showSupportNow: show/hide Support now
  • showSpotNow: show/hide raw spot/electricity price (Spot now)
  • showUsageNow: show/hide live usage pills (Power now, Hour use, Hour export)
  • showGraphNowIndicator: show/hide an on-graph NOW badge + marker on the current consumed bar

Rate now is the final import rate used for cost calculations and includes configured adjustments (for example VAT, surcharges, network tariff rules, and subsidy/policy effects).

Fixed scale and over-limit coloring

  • fixedMaxWh: locks bar scaling to a fixed hourly limit (Wh). Example: 5000 = 5 kWh.
  • If consumed energy for an hour exceeds fixedMaxWh, the consumed bar is clamped to top and shown in red.
  • Bar baseline is drawn as a straight line to make comparison across hours easier.

Pricing profiles (separate files)

Profiles live in MMM-EnergyPulse/profiles/ and are loaded only when pricingProfile.enabled is true.

Note: JSON does not allow comments, so profile files intentionally contain plain JSON values only.

  • Merge order: module pricing defaults -> selected profile file -> pricingProfile.overrides
  • Multi-region is manual by design: switch profile file name in config.

Available profiles

  • Norway: NO1.json, NO2.json, NO3.json, NO4.json, NO5.json
  • Sweden: SE1.json, SE2.json, SE3.json, SE4.json
  • Denmark: DK1.json, DK2.json
  • Finland: FI.json
  • Netherlands: NL.json
  • Germany: DE.json

Activate a profile

pricingProfile: {
  enabled: true,
  name: "NO2.json",
  overrides: {
    adjustments: {
      supplierSurchargePerKWh: 0.05,
      networkTariffRules: [
        {from: "06:00", to: "22:00", addPerKWh: 0.42},
        {from: "22:00", to: "06:00", addPerKWh: 0.31}
      ]
    }
  }
}

If pricingProfile.enabled is false, the module uses your regular pricing block directly.

pricePolicy explained

pricePolicy is the universal name for a fixed/capped import-energy policy (Norgespris-style behavior).

  • fixedImportPerKWh
    • Replaces the spot-based energy component with a fixed value.
    • Use this when you want a fully fixed import-energy price.
  • maxImportPerKWh
    • Caps the energy component at this maximum value.
    • If spot-based energy is lower than the cap, the lower value is used.

How they work together

  • If pricePolicy.enabled is false: both values are ignored.
  • If pricePolicy.enabled is true and only fixedImportPerKWh is set: fixed policy.
  • If pricePolicy.enabled is true and only maxImportPerKWh is set: capped policy.
  • If both are set: fixed value is applied first, then cap is applied (in practice this usually behaves as the lower of the two).

Important scope note

pricePolicy affects the import energy component before network tariff, other surcharges and VAT are added.

Price input units (major vs subunit)

The module always calculates internally in major currency per kWh (NOK/kWh, EUR/kWh, etc.), but your config input can be:

  • inputUnit: "major" → values are already in major units (e.g. 0.50 NOK/kWh)
  • inputUnit: "subunit" → values are in øre/cent (e.g. 50 øre/kWh)
  • inputUnit: "auto" → values above autoSubunitThreshold are treated as subunit and divided by 100

This applies to configurable per-kWh numbers such as:

  • networkTariffRules[].addPerKWh
  • supplierSurchargePerKWh, otherSurchargePerKWh
  • pricePolicy.fixedImportPerKWh, pricePolicy.maxImportPerKWh
  • subsidy.thresholdPerKWh
  • exportCredit.offsetPerKWh, fixedPerKWh, minPerKWh, maxPerKWh

Example for øre-based config:

pricing: {
  inputUnit: "subunit",
  adjustments: {
    networkTariffRules: [
      {from: "06:00", to: "22:00", addPerKWh: 47.66},
      {from: "22:00", to: "06:00", addPerKWh: 32.66}
    ]
  },
  pricePolicy: {
    enabled: true,
    fixedImportPerKWh: 50,
    maxImportPerKWh: 50
  },
  subsidy: {
    enabled: true,
    thresholdPerKWh: 93.75,
    percent: 0.9
  }
}

Example setups

// A) Fully fixed import-energy policy
pricePolicy: {
  enabled: true,
  fixedImportPerKWh: 0.50,
  maxImportPerKWh: null
}

// B) Spot-based with cap
pricePolicy: {
  enabled: true,
  fixedImportPerKWh: null,
  maxImportPerKWh: 0.50
}

// C) Disabled
pricePolicy: {
  enabled: false,
  fixedImportPerKWh: 0.50,
  maxImportPerKWh: 0.50
}

Notes

  • Input should be instantaneous power in Watts.
  • Positive values are treated as consumption, negative values as production.
  • Energy is integrated over time and bucketed by local hour.
  • Persistence survives MagicMirror restart and is automatically pruned.
  • Dynamic pricing formula per hour:
    • spot -> energyMultiplier -> (+ supplier surcharge) -> pricePolicy fixed/cap -> subsidy discount -> (+ network tariff + other surcharge) -> VAT
  • Export credit can be spot-linked or fixed and is applied against produced energy.
  • If both pricePolicy.enabled and subsidy.enabled are true, you can enforce exclusivity with featureToggle.preventSimultaneousPolicyAndSubsidy and choose winner with featureToggle.priority.

For Norway profiles, pricePolicy defaults are prefilled from NVE Norgespris information:

  • 0.50 per kWh (with VAT) for most zones
  • 0.40 per kWh (no VAT) in NO4

Verified online (as of 2026-03-21)

  • Nord Pool day-ahead source is live and available via dataportal-api.nordpoolgroup.com.
  • Current Nord Pool feed includes 15-minute market time units in many areas; this module averages sub-hour entries to hourly prices before cost calculations.
  • EU-level official stats (Eurostat) confirm large country-by-country variation in end-user electricity pricing due to taxes, levies, VAT, network tariffs, and temporary support/cap schemes.
  • Norway household support is reflected in EU official statistics context as an active monthly spot-threshold support model, but exact national implementation details can change and should be kept configurable.

Because these rules vary and can change, MMM-EnergyPulse keeps all pricing policy parts configurable in pricing.

Next steps

  • Add optional fixed hourly fee support (daily/periodic fees split per hour)
  • Add optional tax rules by season/month/day type
  • Add compact or detailed summary modes

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors