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
- 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
cd ~/MagicMirror/modules/MMM-EnergyPulse
npm installAdd 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
}
}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.
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:
- Latest API value (last successful poll)
- Saved persisted value (from last valid API value)
- Fallback
valueinnetworkTariffSources
If saved value is 0, fallback value is used on startup until a new API value arrives.
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 pills are grouped into horizontal stacks (rows):
- Costs row
- Prices row
- Usage row
Visibility options:
showNetworkNow: show/hideNetwork nowin prices rowshowSupportNow: show/hideSupport nowshowSpotNow: 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-graphNOWbadge + 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).
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.
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.
- 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
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 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.
- If
pricePolicy.enabledisfalse: both values are ignored. - If
pricePolicy.enabledistrueand onlyfixedImportPerKWhis set: fixed policy. - If
pricePolicy.enabledistrueand onlymaxImportPerKWhis 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).
pricePolicy affects the import energy component before network tariff, other surcharges and VAT are added.
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.50NOK/kWh)inputUnit: "subunit"→ values are in øre/cent (e.g.50øre/kWh)inputUnit: "auto"→ values aboveautoSubunitThresholdare treated as subunit and divided by 100
This applies to configurable per-kWh numbers such as:
networkTariffRules[].addPerKWhsupplierSurchargePerKWh,otherSurchargePerKWhpricePolicy.fixedImportPerKWh,pricePolicy.maxImportPerKWhsubsidy.thresholdPerKWhexportCredit.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
}
}// 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
}- 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.enabledandsubsidy.enabledaretrue, you can enforce exclusivity withfeatureToggle.preventSimultaneousPolicyAndSubsidyand choose winner withfeatureToggle.priority.
For Norway profiles, pricePolicy defaults are prefilled from NVE Norgespris information:
0.50per kWh (with VAT) for most zones0.40per kWh (no VAT) inNO4
- 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.
- 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
