Real-time MQTT telemetry from MarkeDroid + Shelly smart relays = automated energy management
The MarkeDroid (MD) device exposes real-time telemetry data via MQTT topics. This makes it possible to integrate with Shelly smart relays and plugs, enabling automated control of loads based on solar production, battery state of charge, grid export, and more.
Shelly devices can subscribe directly to the MD MQTT broker and run local scripts that react to incoming data — no cloud, no Home Assistant, no additional hardware required.
With real-time access to solar, load, battery, and grid data, you can automate a wide variety of energy management scenarios. Click any use case to jump to the script:
The MD device publishes real-time data on the following topics:
{"Component":"solar","W":2040,"SolarWCapacity":63000,"TotWhInc":3.33,"time":1778172926379,"ExpLimiterState":0,"SinceLast":5.816}
| Field | Description |
|---|---|
W | Current solar production in Watts |
SolarWCapacity | Total installed PV capacity in Watts |
TotWhInc | Total Wh increment since last message |
ExpLimiterState | Export limiter state: 0 = not active (sell as usual), 1 = active (sell 0W, negative price), 2 = error state |
time | Unix timestamp (ms) |
SinceLast | Seconds since last update |
{"Component":"load","W":2765,"TotWhInc":3.12,"time":1778141583536,"SinceLast":4.005}
| Field | Description |
|---|---|
W | Current load consumption in Watts |
TotWhInc | Total Wh increment since last message |
time | Unix timestamp (ms) |
SinceLast | Seconds since last update |
{"Component":"battery","W":-126,"SoC":24,"CapacityWh":184320,"BatTemp":20,"DCI":-2.4,"TotWhChaInc":0,"TotWhDisChaInc":0.19,"time":1778141583537,"SinceLast":4.514}
| Field | Description |
|---|---|
W | Battery power (negative = charging, positive = discharging) |
SoC | State of Charge (%) |
CapacityWh | Total battery capacity in Wh |
BatTemp | Battery temperature (°C) |
DCI | DC current (A) |
TotWhChaInc | Wh charged increment |
TotWhDisChaInc | Wh discharged increment |
time | Unix timestamp (ms) |
SinceLast | Seconds since last update |
{"Component":"meter","W":-10010,"WphA":-3372,"WphB":-2974,"WphC":-3626,"VphA":234.44,"VphB":234.40,"VphC":235.68,"TotWhImpInc":0,"TotWhExpInc":12.3,"time":1778141585967,"SinceLast":4.466}
| Field | Description |
|---|---|
W | Grid power (negative = exporting, positive = importing) |
WphA/B/C | Power per phase (W) |
VphA/B/C | Voltage per phase (V) |
TotWhImpInc | Wh imported increment |
TotWhExpInc | Wh exported increment |
time | Unix timestamp (ms) |
SinceLast | Seconds since last update |
Configure MQTT in the Shelly Web Interface (not the script editor):
http://192.168.1.xx)markedroid.local:1883) — if mDNS/Bonjour doesn't resolve, use the MD device LAN IP (e.g., 192.168.1.100:1883)Once connected, the Shelly can subscribe to any MD telemetry topic and react in real-time via its built-in scripting engine.
Click "Copy" to copy any script to clipboard, then paste into your Shelly Script Editor
When the export limiter is active (ExpLimiterState = 1), it means the energy price is negative and MarkeDroid has stopped selling to the grid. If the battery is also near full, PV power may get curtailed. This is the perfect time to turn on heavy loads — water heater, pool pump, EV charger — and use that free energy before it's wasted.
⚠️ Note: The ExpLimiterState field is being gradually rolled out and may not yet be available for all customers. If your solar MQTT payload does not include this field, this script will not trigger.
socMin — minimum SoC to activate loads (battery should be near full), relayID — relay for your heavy load// Negative price dump load
// When ExpLimiterState = 1 (negative price, selling disabled)
// AND battery is near full -> turn on heavy loads
// Don't waste free energy, use it!
let CONFIG = {
solarTopic: "public/realtimedata/solar",
batteryTopic: "public/realtimedata/battery",
relayID: 0,
socMin: 80 // Only activate when battery is above 80%
};
let expLimiter = 0;
let soc = 0;
let loadOn = false;
MQTT.subscribe(CONFIG.solarTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.ExpLimiterState !== 'undefined') {
expLimiter = data.ExpLimiterState;
print("Export Limiter: " + expLimiter + " (0=sell, 1=blocked, 2=error)");
evaluate();
}
});
MQTT.subscribe(CONFIG.batteryTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.SoC !== 'undefined') {
soc = data.SoC;
evaluate();
}
});
function evaluate() {
// Negative price + battery near full = burn energy!
if (expLimiter === 1 && soc >= CONFIG.socMin && !loadOn) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: true });
loadOn = true;
print("LOAD ON - Negative price! SoC: " + soc + "% - Use it or lose it!");
}
// Turn off when price is back to normal OR battery dropped
if (loadOn && (expLimiter !== 1 || soc < CONFIG.socMin - 10)) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: false });
loadOn = false;
print("LOAD OFF - Price normal or battery needs charge");
}
}
print("Script started: Negative price dump load (ExpLimiterState)");
Shuts down microinverters connected to the GEN port via a Shelly relay and contactor. Triggers when the battery is full (no need to charge) or when the energy price is negative (ExpLimiterState = 1). Re-enables microinverters when the battery needs charging and price is back to normal.
⚠️ Note: The ExpLimiterState field is being gradually rolled out and may not yet be available for all customers. The battery SoC guard works independently of this field.
socFull — disable above this % (battery full), socNeedsCharge — re-enable below this % (battery needs charging), relayID — Shelly relay controlling the contactor// Disable microinverters on GEN port via Shelly relay + contactor
// Triggers: battery full OR negative price (ExpLimiterState = 1)
// Re-enables when battery needs charging AND price is normal
let CONFIG = {
batteryTopic: "public/realtimedata/battery",
solarTopic: "public/realtimedata/solar",
relayID: 0,
socFull: 95, // Disable microinverters above 95% (battery full)
socNeedsCharge: 70 // Re-enable below 70% (battery needs charging)
};
let soc = 0;
let expLimiter = 0;
let relayState = true; // Assume ON at start (microinverters running)
MQTT.subscribe(CONFIG.batteryTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.SoC !== 'undefined') {
soc = data.SoC;
print("Battery SoC: " + soc + "%");
evaluate();
}
});
MQTT.subscribe(CONFIG.solarTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.ExpLimiterState !== 'undefined') {
expLimiter = data.ExpLimiterState;
print("Export Limiter: " + expLimiter);
evaluate();
}
});
function evaluate() {
// Disable when: battery full OR negative price
let shouldDisable = (soc >= CONFIG.socFull) || (expLimiter === 1);
// Re-enable when: battery needs charging AND price is normal
let shouldEnable = (soc <= CONFIG.socNeedsCharge) && (expLimiter !== 1);
if (shouldDisable && relayState === true) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: false });
relayState = false;
let reason = (expLimiter === 1) ? "Negative price" : "Battery full";
print("Microinverters DISABLED - " + reason + " (SoC: " + soc + "%)");
} else if (shouldEnable && relayState === false) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: true });
relayState = true;
print("Microinverters ENABLED - Battery needs charge: " + soc + "%");
}
}
print("Script started: Microinverter guard (SoC + negative price)");
Turns a relay on as soon as solar production rises above 3kW, and off only after production stays below 1kW continuously for 5 minutes (hysteresis prevents chattering on brief dips).
onThreshold — Watts to turn on, offThreshold — Watts to start off-countdown, offDelayMs — how long below off-threshold before turning off// Configuration
let CONFIG = {
topic: "public/realtimedata/solar",
onThreshold: 3000, // Turn ON above 3,000 W
offThreshold: 1000, // Consider turning OFF below 1,000 W
offDelayMs: 5 * 60 * 1000, // Must stay below offThreshold this long before OFF (5 min)
relayID: 0 // Usually 0 for Shelly Plug
};
// Handle for the pending OFF timer (null when no timer is armed)
let offTimer = null;
function cancelOffTimer() {
if (offTimer !== null) {
Timer.clear(offTimer);
offTimer = null;
}
}
// Subscribe to the solar topic
MQTT.subscribe(CONFIG.topic, function(topic, message) {
// Parse the incoming JSON string
let data = JSON.parse(message);
// Check if the "W" property exists in the message
if (data && typeof data.W !== 'undefined') {
let currentPower = data.W;
print("Current Solar Power: ", currentPower, "W");
if (currentPower > CONFIG.onThreshold) {
// Above 3kW: turn ON immediately and cancel any pending OFF
cancelOffTimer();
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: true });
print("Status: Above 3kW - Turning ON");
} else if (currentPower < CONFIG.offThreshold) {
// Below 1kW: arm a one-shot OFF timer if not already armed
if (offTimer === null) {
print("Status: Below 1kW - starting 5 min OFF countdown");
offTimer = Timer.set(CONFIG.offDelayMs, false, function() {
offTimer = null;
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: false });
print("Status: Below 1kW for 5 min - Turning OFF");
});
}
} else {
// Between 1kW and 3kW: recovered above OFF threshold, cancel countdown
cancelOffTimer();
}
} else {
print("Error: Received message but could not find 'W' property.");
}
});
print("Script started: Monitoring solar power on topic " + CONFIG.topic);
Activates pool heater only when solar production exceeds household load by a minimum surplus.
minSurplus — minimum Watts of surplus before enabling heaterlet CONFIG = {
solarTopic: "public/realtimedata/solar",
loadTopic: "public/realtimedata/load",
relayID: 0,
minSurplus: 2000 // Minimum 2kW surplus before starting heater
};
let solar = 0;
let load = 0;
MQTT.subscribe(CONFIG.solarTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.W !== 'undefined') {
solar = data.W;
evaluateRelay();
}
});
MQTT.subscribe(CONFIG.loadTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.W !== 'undefined') {
load = data.W;
evaluateRelay();
}
});
function evaluateRelay() {
let surplus = solar - load;
print("Surplus: ", surplus, "W");
if (surplus > CONFIG.minSurplus) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: true });
print("Pool heater ON - Surplus: " + surplus + "W");
} else {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: false });
print("Pool heater OFF - Surplus: " + surplus + "W");
}
}
print("Script started: Pool heater surplus control");
When exporting significant power to the grid, diverts excess energy to a resistive load (water heater/boiler).
exportThreshold — negative value, how much export triggers the dump loadlet CONFIG = {
meterTopic: "public/realtimedata/meter",
relayID: 0,
exportThreshold: -2000 // Start dumping when exporting more than 2kW
};
MQTT.subscribe(CONFIG.meterTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.W !== 'undefined') {
let gridPower = data.W;
print("Grid: ", gridPower, "W");
if (gridPower < CONFIG.exportThreshold) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: true });
print("Water heater ON - Exporting " + (-gridPower) + "W to grid");
} else {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: false });
print("Water heater OFF");
}
}
});
print("Script started: Zero-export water heater dump");
Enables EV charging contactor only when grid export exceeds minimum single-phase charge rate (~3.5kW).
exportMin — min export to start, hysteresis — threshold to stoplet CONFIG = {
meterTopic: "public/realtimedata/meter",
relayID: 0,
exportMin: -3500, // At least 3.5kW export before enabling
hysteresis: -1000 // Keep charging until export drops below 1kW
};
let charging = false;
MQTT.subscribe(CONFIG.meterTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.W !== 'undefined') {
let gridPower = data.W;
if (!charging && gridPower < CONFIG.exportMin) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: true });
charging = true;
print("EV Charger ENABLED - Exporting " + (-gridPower) + "W");
} else if (charging && gridPower > CONFIG.hysteresis) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: false });
charging = false;
print("EV Charger DISABLED - Insufficient surplus");
}
}
});
print("Script started: EV surplus charging");
When battery temperature gets too hot, activates a cooling fan or AC unit. Turns off cooling when temperature drops to safe level.
maxTemp — start cooling above this °C, safeTemp — stop cooling below this °Clet CONFIG = {
batteryTopic: "public/realtimedata/battery",
relayID: 0, // Relay connected to cooling fan / AC
maxTemp: 40, // Start cooling above 40°C
safeTemp: 33 // Stop cooling below 33°C
};
let coolingOn = false;
MQTT.subscribe(CONFIG.batteryTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.BatTemp !== 'undefined') {
let temp = data.BatTemp;
print("Battery Temp: " + temp + "°C");
if (temp >= CONFIG.maxTemp && !coolingOn) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: true });
coolingOn = true;
print("COOLING ON - Battery too hot: " + temp + "°C");
} else if (temp <= CONFIG.safeTemp && coolingOn) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: false });
coolingOn = false;
print("COOLING OFF - Battery cooled down: " + temp + "°C");
}
}
});
print("Script started: Battery cooling control");
For batteries without built-in heating. When temperature drops too low, activates a heater pad or space heater to keep batteries in safe operating range. Prevents charging/discharging damage in cold climates.
minTemp — start heating below this °C, warmTemp — stop heating above this °Clet CONFIG = {
batteryTopic: "public/realtimedata/battery",
relayID: 0, // Relay connected to heater pad / space heater
minTemp: 5, // Start heating below 5°C
warmTemp: 12 // Stop heating above 12°C
};
let heatingOn = false;
MQTT.subscribe(CONFIG.batteryTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.BatTemp !== 'undefined') {
let temp = data.BatTemp;
print("Battery Temp: " + temp + "°C");
if (temp <= CONFIG.minTemp && !heatingOn) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: true });
heatingOn = true;
print("HEATING ON - Battery too cold: " + temp + "°C");
} else if (temp >= CONFIG.warmTemp && heatingOn) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: false });
heatingOn = false;
print("HEATING OFF - Battery warm enough: " + temp + "°C");
}
}
});
print("Script started: Battery heating control (for batteries without built-in heater)");
Cuts non-essential loads when grid import exceeds a budget. Useful during expensive tariff periods.
maxImport — maximum allowed grid import in Wattslet CONFIG = {
meterTopic: "public/realtimedata/meter",
relayID: 0,
maxImport: 3000 // Cut non-essential loads above 3kW import
};
MQTT.subscribe(CONFIG.meterTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.W !== 'undefined') {
let gridPower = data.W;
if (gridPower > CONFIG.maxImport) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: false });
print("Load shedding - Grid import: " + gridPower + "W");
} else {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: true });
print("Loads restored - Grid import: " + gridPower + "W");
}
}
});
print("Script started: Grid import limiter");
When battery is nearly full and still exporting, activate dump loads to use excess energy productively.
socFull — SoC % to consider battery full, exportMin — min export to triggerlet CONFIG = {
batteryTopic: "public/realtimedata/battery",
meterTopic: "public/realtimedata/meter",
relayID: 0,
socFull: 98,
exportMin: -1000
};
let soc = 0;
let gridW = 0;
MQTT.subscribe(CONFIG.batteryTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.SoC !== 'undefined') {
soc = data.SoC;
evaluate();
}
});
MQTT.subscribe(CONFIG.meterTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.W !== 'undefined') {
gridW = data.W;
evaluate();
}
});
function evaluate() {
if (soc >= CONFIG.socFull && gridW < CONFIG.exportMin) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: true });
print("Dump load ON - Battery full (" + soc + "%), exporting " + (-gridW) + "W");
} else {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: false });
print("Dump load OFF - SoC: " + soc + "%, Grid: " + gridW + "W");
}
}
print("Script started: Full battery dump load");
Shelly Power Strip with 4 independent relays. Each relay activates at a different solar surplus threshold, progressively adding loads as more free energy becomes available.
relays[].threshold — surplus Watts to activate each relay. Adjust per connected load size.// Shelly Power Strip - 4 relays, each with its own surplus threshold
// Relay 0: Small load (e.g., phone charger) at 500W surplus
// Relay 1: Medium load (e.g., fan/lamp) at 1500W surplus
// Relay 2: Larger load (e.g., dehumidifier) at 3000W surplus
// Relay 3: Heavy load (e.g., heater element) at 5000W surplus
let CONFIG = {
solarTopic: "public/realtimedata/solar",
loadTopic: "public/realtimedata/load",
relays: [
{ id: 0, threshold: 500, name: "Phone charger" },
{ id: 1, threshold: 1500, name: "Fan" },
{ id: 2, threshold: 3000, name: "Dehumidifier" },
{ id: 3, threshold: 5000, name: "Heater element" }
],
hysteresis: 200 // Watts below threshold to turn off (prevents chattering)
};
let solar = 0;
let load = 0;
let relayStates = [false, false, false, false];
MQTT.subscribe(CONFIG.solarTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.W !== 'undefined') {
solar = data.W;
evaluateRelays();
}
});
MQTT.subscribe(CONFIG.loadTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.W !== 'undefined') {
load = data.W;
evaluateRelays();
}
});
function evaluateRelays() {
let surplus = solar - load;
print("Surplus: " + surplus + "W");
for (let i = 0; i < CONFIG.relays.length; i++) {
let relay = CONFIG.relays[i];
let shouldBeOn = surplus > relay.threshold;
let shouldBeOff = surplus < (relay.threshold - CONFIG.hysteresis);
if (shouldBeOn && !relayStates[i]) {
Shelly.call("Switch.Set", { id: relay.id, on: true });
relayStates[i] = true;
print("Relay " + relay.id + " ON (" + relay.name + ") - Surplus: " + surplus + "W");
} else if (shouldBeOff && relayStates[i]) {
Shelly.call("Switch.Set", { id: relay.id, on: false });
relayStates[i] = false;
print("Relay " + relay.id + " OFF (" + relay.name + ") - Surplus: " + surplus + "W");
}
}
}
print("Script started: Power Strip 4-relay staged surplus control");
Use a Shelly PM to switch external 3-phase contactors for high-power loads (heat pumps, large compressors, industrial equipment). The Shelly drives the contactor coil, not the load directly. Activates when sufficient solar surplus is available across all phases.
surplusThreshold — minimum total surplus to engage contactor, minPerPhase — ensure no single phase is importing// Shelly PM driving a 3-phase contactor coil
// The contactor switches the actual high-power load (e.g., 11kW heat pump)
// Shelly only needs to handle the coil current (~0.5A at 230V)
let CONFIG = {
meterTopic: "public/realtimedata/meter",
relayID: 0,
surplusThreshold: -8000, // Total export must exceed 8kW
minPerPhase: -1000, // Each phase must export at least 1kW
hysteresis: -5000 // Keep contactor on until export drops below 5kW
};
let contactorOn = false;
MQTT.subscribe(CONFIG.meterTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.W !== 'undefined') {
let total = data.W;
let phA = data.WphA;
let phB = data.WphB;
let phC = data.WphC;
print("Grid: " + total + "W (A:" + phA + " B:" + phB + " C:" + phC + ")");
// All phases must be exporting enough
let allPhasesOk = phA < CONFIG.minPerPhase &&
phB < CONFIG.minPerPhase &&
phC < CONFIG.minPerPhase;
if (!contactorOn && total < CONFIG.surplusThreshold && allPhasesOk) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: true });
contactorOn = true;
print("3-PHASE CONTACTOR ENGAGED - Surplus: " + (-total) + "W");
} else if (contactorOn && total > CONFIG.hysteresis) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: false });
contactorOn = false;
print("3-PHASE CONTACTOR RELEASED - Insufficient surplus");
}
}
});
print("Script started: 3-phase contactor control via Shelly PM");
Starts a backup generator when grid voltage is 0 (blackout) AND battery SoC is critically low. Uses Shelly PM to trigger the generator's remote start relay. Stops generator when battery recovers or grid returns.
socCritical — SoC % to trigger start, socRecover — SoC % to stop generator, gridDownV — voltage threshold to detect blackout// Emergency generator auto-start
// Conditions: Grid voltage = 0 (blackout) AND battery SoC critically low
// Shelly PM relay triggers the generator's remote start input
let CONFIG = {
batteryTopic: "public/realtimedata/battery",
meterTopic: "public/realtimedata/meter",
relayID: 0,
socCritical: 10, // Start generator below 10% SoC
socRecover: 50, // Stop generator when SoC reaches 50%
gridDownV: 50 // Consider grid down if all phases below 50V
};
let soc = 100;
let gridVoltageOk = true;
let generatorRunning = false;
MQTT.subscribe(CONFIG.batteryTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.SoC !== 'undefined') {
soc = data.SoC;
evaluate();
}
});
MQTT.subscribe(CONFIG.meterTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.VphA !== 'undefined') {
// Grid is down if all phase voltages are near zero
gridVoltageOk = data.VphA > CONFIG.gridDownV ||
data.VphB > CONFIG.gridDownV ||
data.VphC > CONFIG.gridDownV;
print("Grid voltage: A=" + data.VphA + "V B=" + data.VphB + "V C=" + data.VphC + "V");
evaluate();
}
});
function evaluate() {
let gridDown = !gridVoltageOk;
print("SoC: " + soc + "%, Grid: " + (gridDown ? "DOWN" : "OK") +
", Generator: " + (generatorRunning ? "RUNNING" : "OFF"));
// START condition: grid is down AND battery critically low
if (!generatorRunning && gridDown && soc <= CONFIG.socCritical) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: true });
generatorRunning = true;
print("GENERATOR STARTED - Grid down, SoC critical: " + soc + "%");
}
// STOP condition: battery recovered OR grid is back
if (generatorRunning && (soc >= CONFIG.socRecover || gridVoltageOk)) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: false });
generatorRunning = false;
if (gridVoltageOk) {
print("GENERATOR STOPPED - Grid restored");
} else {
print("GENERATOR STOPPED - Battery recovered: " + soc + "%");
}
}
}
print("Script started: Emergency generator auto-start (grid down + low SoC)");
Uses a Shelly Multicolor Bulb (E27 Gen3) as a visual grid-down indicator. When all 3 phase voltages drop below 50V, the bulb turns on with a red warning color. When grid returns, switches to green briefly then turns off. Works great as a status light in a hallway or technical room.
gridDownV — voltage threshold to detect blackout. Adjust RGB values to change warning color.// Grid-down warning light using Shelly Multicolor Bulb E27 Gen3
// RED = grid is down (all phases below threshold)
// GREEN flash = grid restored, then bulb turns off
// Uses Light.Set API for RGB color control
let CONFIG = {
meterTopic: "public/realtimedata/meter",
lightID: 0,
gridDownV: 50 // All phases must be below this to trigger
};
let alertActive = false;
MQTT.subscribe(CONFIG.meterTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.VphA !== 'undefined') {
let vA = data.VphA;
let vB = data.VphB;
let vC = data.VphC;
let gridDown = vA < CONFIG.gridDownV &&
vB < CONFIG.gridDownV &&
vC < CONFIG.gridDownV;
print("Voltage: A=" + vA + "V B=" + vB + "V C=" + vC + "V -> " + (gridDown ? "DOWN" : "OK"));
if (gridDown && !alertActive) {
// Turn bulb ON with RED color at full brightness
Shelly.call("Light.Set", {
id: CONFIG.lightID,
on: true,
red: 255,
green: 0,
blue: 0,
brightness: 100
});
alertActive = true;
print("GRID DOWN - Bulb RED warning!");
} else if (!gridDown && alertActive) {
// Grid restored - flash GREEN briefly then turn off
Shelly.call("Light.Set", {
id: CONFIG.lightID,
on: true,
red: 0,
green: 255,
blue: 0,
brightness: 100
});
print("GRID RESTORED - Bulb GREEN");
// Turn off after 10 seconds
Timer.set(10000, false, function() {
Shelly.call("Light.Set", {
id: CONFIG.lightID,
on: false
});
print("Indicator light OFF");
});
alertActive = false;
}
}
});
print("Script started: Grid-down color indicator (Shelly Multicolor Bulb)");
Uses solar production as a light sensor — when solar drops to 0W it's dark outside, turn on outdoor lights, garden lights, Christmas lights, etc. When solar starts producing again in the morning, turn them off. No separate light sensor needed.
darkThreshold — Watts below which it's considered dark, dawnThreshold — Watts above which it's morning// Dusk/Dawn outdoor lights using solar production as a light sensor
// Solar = 0W means it's dark outside -> lights ON
// Solar starts producing -> it's morning -> lights OFF
// No external light sensor needed, MD solar telemetry does the job
let CONFIG = {
solarTopic: "public/realtimedata/solar",
relayID: 0,
darkThreshold: 10, // Below 10W = dark (panels produce nothing)
dawnThreshold: 50 // Above 50W = sunrise (hysteresis to avoid flicker)
};
let lightsOn = false;
MQTT.subscribe(CONFIG.solarTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.W !== 'undefined') {
let solar = data.W;
print("Solar: " + solar + "W");
if (solar <= CONFIG.darkThreshold && !lightsOn) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: true });
lightsOn = true;
print("LIGHTS ON - It's dark (solar: " + solar + "W)");
} else if (solar >= CONFIG.dawnThreshold && lightsOn) {
Shelly.call("Switch.Set", { id: CONFIG.relayID, on: false });
lightsOn = false;
print("LIGHTS OFF - Sunrise detected (solar: " + solar + "W)");
}
}
});
print("Script started: Dusk/dawn outdoor lights via solar production");
A household-friendly energy status bulb. RED = low solar and battery below 50% — conserve energy! GREEN = battery above 50% and 5kW+ solar — spend freely, run the dryer, dishwasher, whatever you like! YELLOW = somewhere in between. Place it in the kitchen or hallway so everyone knows the energy situation at a glance.
socLow — SoC threshold for red, solarGood — solar Watts for green mode// "Wife Indicator" - Kitchen energy traffic light
// RED = Low solar + battery below 50% -> save energy!
// YELLOW = Some solar or battery OK, but not both -> use wisely
// GREEN = Battery > 50% AND solar > 5kW -> spend as much as you like!
// Uses Shelly Multicolor Bulb E27 Gen3
let CONFIG = {
solarTopic: "public/realtimedata/solar",
batteryTopic: "public/realtimedata/battery",
lightID: 0,
socLow: 50, // Below 50% = battery is getting low
solarGood: 5000 // 5kW+ solar = plenty of free energy
};
let solar = 0;
let soc = 0;
let currentColor = "";
MQTT.subscribe(CONFIG.solarTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.W !== 'undefined') {
solar = data.W;
updateLight();
}
});
MQTT.subscribe(CONFIG.batteryTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.SoC !== 'undefined') {
soc = data.SoC;
updateLight();
}
});
function updateLight() {
let color = "";
let r = 0, g = 0, b = 0;
if (soc >= CONFIG.socLow && solar >= CONFIG.solarGood) {
// GREEN - plenty of energy, spend freely!
color = "green";
r = 0; g = 255; b = 0;
} else if (soc < CONFIG.socLow && solar < 500) {
// RED - low battery, no solar, conserve!
color = "red";
r = 255; g = 0; b = 0;
} else {
// YELLOW - in between, use wisely
color = "yellow";
r = 255; g = 180; b = 0;
}
// Only update bulb when color actually changes
if (color !== currentColor) {
Shelly.call("Light.Set", {
id: CONFIG.lightID,
on: true,
red: r,
green: g,
blue: b,
brightness: 80
});
currentColor = color;
print("Energy status: " + color.toUpperCase() +
" (Solar: " + solar + "W, SoC: " + soc + "%)");
}
}
print("Script started: Wife Indicator - energy budget traffic light");
Controls an SG Ready heat pump using two relay signals (SG-1 & SG-2). SG Ready defines 4 operating modes via 2 bits: Normal (both off), Recommended ON (slightly increased), Boost (full power on surplus), and Blocked (forced off during peak tariff). This script reads grid meter data from MarkeDroid and automatically selects the best mode. Use a Shelly Pro 2 (2 dry contacts, DIN-rail) or 2× Shelly Plus 1 wired to the heat pump’s SG-1 and SG-2 terminals.
boostThreshold — surplus Watts to activate boost mode, recommendThreshold — surplus for recommended ON, normalThreshold — import level to return to normal, sg1RelayID / sg2RelayID — relay IDs for SG-1 and SG-2// SG Ready heat pump control via 2 Shelly relays
// SG-1 and SG-2 signal the heat pump which mode to use:
//
// SG-1 | SG-2 | Mode
// OFF | OFF | 1 - Normal operation
// OFF | ON | 2 - Recommended ON (eco boost)
// ON | OFF | 3 - Forced OFF (blocking)
// ON | ON | 4 - Boost mode (full power)
//
// Wiring: Shelly Pro 2 relay 0 -> SG-1, relay 1 -> SG-2
// or 2x Shelly Plus 1, each controlling one signal
let CONFIG = {
meterTopic: "public/realtimedata/meter",
sg1RelayID: 0, // Relay for SG-1 signal
sg2RelayID: 1, // Relay for SG-2 signal
boostThreshold: -3000, // Export > 3kW -> Boost (mode 4)
recommendThreshold: -1000, // Export > 1kW -> Recommended ON (mode 2)
normalThreshold: 500, // Import > 500W -> Normal (mode 1)
hysteresis: 300 // Watts buffer to prevent mode flapping
};
let currentMode = 1; // Start in normal mode
function setSGMode(mode) {
if (mode === currentMode) return;
let sg1 = false;
let sg2 = false;
if (mode === 1) {
// Normal: SG-1 OFF, SG-2 OFF
sg1 = false; sg2 = false;
} else if (mode === 2) {
// Recommended ON: SG-1 OFF, SG-2 ON
sg1 = false; sg2 = true;
} else if (mode === 3) {
// Forced OFF (blocking): SG-1 ON, SG-2 OFF
sg1 = true; sg2 = false;
} else if (mode === 4) {
// Boost: SG-1 ON, SG-2 ON
sg1 = true; sg2 = true;
}
Shelly.call("Switch.Set", { id: CONFIG.sg1RelayID, on: sg1 });
Shelly.call("Switch.Set", { id: CONFIG.sg2RelayID, on: sg2 });
currentMode = mode;
print("SG Ready mode " + mode + " (SG-1: " + sg1 + ", SG-2: " + sg2 + ")");
}
MQTT.subscribe(CONFIG.meterTopic, function(topic, message) {
let data = JSON.parse(message);
if (data && typeof data.W !== 'undefined') {
let grid = data.W; // Negative = exporting, Positive = importing
print("Grid: " + grid + "W (mode " + currentMode + ")");
if (grid < CONFIG.boostThreshold - CONFIG.hysteresis) {
// Large surplus -> Boost mode (full power)
setSGMode(4);
} else if (grid < CONFIG.recommendThreshold - CONFIG.hysteresis) {
// Moderate surplus -> Recommended ON
setSGMode(2);
} else if (grid > CONFIG.normalThreshold + CONFIG.hysteresis) {
// Importing from grid -> Normal operation
setSGMode(1);
}
// In the hysteresis zone, keep current mode (no change)
}
});
print("Script started: SG Ready heat pump control");
print("Mode 1=Normal, 2=Eco boost, 3=Blocked, 4=Full boost");