Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save Kinggrass/be1dc4b9cefd4eb0b4988e6492484083 to your computer and use it in GitHub Desktop.

Select an option

Save Kinggrass/be1dc4b9cefd4eb0b4988e6492484083 to your computer and use it in GitHub Desktop.
Shelly Pro 3EM: Saldierende Energiemessung (Net Metering) mit Home Assistant Auto-Discovery
/**
* Shelly Pro 3EM - Net Metering (Saldierung) & Home Assistant Auto-Discovery
*
* This script calculates the total active power across all 3 phases (A+B+C).
* It simulates a "net metering" utility meter:
* - If Total Power > 0: Increases "Import" counter
* - If Total Power < 0: Increases "Export" counter
*
* FEATURES:
* - Precise integration (runs every 0.5s)
* - Persists counters to KVS (flash memory) to survive reboots
* - MQTT Auto-Discovery: Automatically creates sensors in Home Assistant
*
* SETUP:
* 1. Enable MQTT in Shelly Settings
* 2. Add this script in the "Scripts" section
* 3. Enable "Start on boot"
*/
let CONFIG = {
updateInterval: 500, // Calculation cycle in ms
saveInterval: 300, // Save to KVS every 300 cycles (~2.5 min)
mqttPrefix: "homeassistant", // Standard HA discovery prefix
};
let SHELLY_ID = undefined;
let energyReturnedWs = 0.0;
let energyConsumedWs = 0.0;
let energyReturnedKWh = 0.0;
let energyConsumedKWh = 0.0;
let saveCounter = 0;
let lastPublishedConsumed = "";
// 1. Get Device ID and start
Shelly.call("Mqtt.GetConfig", "", function (res, err_code, err_msg, ud) {
SHELLY_ID = res["topic_prefix"];
if (!SHELLY_ID) {
print("ERROR: MQTT not configured or no topic_prefix found!");
} else {
print("Shelly ID found: " + SHELLY_ID);
LoadCounters();
// Announce device to Home Assistant after a short delay
Timer.set(2000, false, AnnounceHA);
}
});
// 2. Home Assistant Auto-Discovery
function AnnounceHA() {
let haTopic = CONFIG.mqttPrefix + "/sensor/shellypro3em-" + SHELLY_ID;
let deviceData = {
"ids": [SHELLY_ID],
"name": "Shelly Pro 3EM",
"mf": "Shelly",
"mdl": "Pro 3EM",
"sw": "Script-Saldierung"
};
// Sensor 1: Import (Grid Consumption)
let payloadImport = JSON.stringify({
"name": "Saldierend Import",
"uniq_id": SHELLY_ID + "_sald_import",
"stat_t": SHELLY_ID + "/energy_counter/consumed",
"unit_of_meas": "kWh",
"dev_cla": "energy",
"stat_cla": "total_increasing",
"dev": deviceData
});
MQTT.publish(haTopic + "-import/config", payloadImport, 0, true);
// Sensor 2: Export (Return to Grid)
let payloadExport = JSON.stringify({
"name": "Saldierend Export",
"uniq_id": SHELLY_ID + "_sald_export",
"stat_t": SHELLY_ID + "/energy_counter/returned",
"unit_of_meas": "kWh",
"dev_cla": "energy",
"stat_cla": "total_increasing",
"dev": deviceData
});
MQTT.publish(haTopic + "-export/config", payloadExport, 0, true);
print("Home Assistant Auto-Discovery sent.");
}
// 3. Load/Save Persistence (KVS)
function LoadCounters() {
Shelly.call("KVS.Get", { "key": "EnergyConsumedKWh" }, function (res) {
if (res && res.value) energyConsumedKWh = Number(res.value);
});
Shelly.call("KVS.Get", { "key": "EnergyReturnedKWh" }, function (res) {
if (res && res.value) energyReturnedKWh = Number(res.value);
});
}
function SaveCounters() {
Shelly.call("KVS.Set", { "key": "EnergyConsumedKWh", "value": energyConsumedKWh });
Shelly.call("KVS.Set", { "key": "EnergyReturnedKWh", "value": energyReturnedKWh });
print("Counters saved to KVS.");
}
// 4. Main Calculation Loop
Timer.set(CONFIG.updateInterval, true, function () {
if (!SHELLY_ID) return;
let em = Shelly.getComponentStatus("em", 0);
if (typeof em.total_act_power !== 'undefined') {
let power = em.total_act_power;
let energyStep = power * (CONFIG.updateInterval / 1000.0); // Energy in Watt-Seconds
// Logic: Net Metering
if (power >= 0) {
energyConsumedWs += energyStep;
} else {
energyReturnedWs -= energyStep; // power is negative, make positive for counter
}
// Convert Ws to kWh (1 kWh = 3,600,000 Ws)
// We use a small buffer (3600 Ws = 1 Wh) to reduce float errors
if (energyConsumedWs >= 3600) {
let chunk = Math.floor(energyConsumedWs / 3600);
energyConsumedKWh += chunk / 1000.0;
energyConsumedWs -= chunk * 3600;
}
if (energyReturnedWs >= 3600) {
let chunk = Math.floor(energyReturnedWs / 3600);
energyReturnedKWh += chunk / 1000.0;
energyReturnedWs -= chunk * 3600;
}
// Publish to MQTT if value changed
let valC = energyConsumedKWh.toFixed(3);
let valR = energyReturnedKWh.toFixed(3);
if (valC !== lastPublishedConsumed) {
MQTT.publish(SHELLY_ID + "/energy_counter/consumed", valC, 0, false);
MQTT.publish(SHELLY_ID + "/energy_counter/returned", valR, 0, false);
lastPublishedConsumed = valC;
}
// Periodic Save
saveCounter++;
if (saveCounter >= CONFIG.saveInterval) {
saveCounter = 0;
SaveCounters();
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment