Shelly LoRa Script

Schaltzustände per Funk spiegeln (und an Shelly Pro weiterreichen)

LoRa im Shelly-Universum ist ein bisschen wie ein geheimer Nebenkanal: extrem weitreichend, robust durch Wände – und perfekt für simple Steuerbefehle. In diesem Praxistest baue ich genau das, was man im Alltag wirklich braucht:

  • Shelly 1 (Sender) wird geschaltet
  • Shelly 2 (Empfänger/Gateway) empfängt den Zustand per LoRa
  • und reicht das Signal per HTTP an einen Shelly Pro weiter, der selbst kein LoRa kann

Das Ganze läuft stabil, schnell (mit sinnvollen Funkparametern) und mit einem einfachen Schutz, damit nicht irgendein fremder LoRa-Knoten dein Licht toggelt.

Der Star der Show (und Fokus dieses Artikels): Shelly LoRa Script.


Grundlagen: LoRa im Shelly-Umfeld

Setup im Überblick

Hardware:

  • Shelly (bezahlter Link) (Gen3/Gen4, egal – Hauptsache: Scripting)
  • 2x LoRa Addon (bezahlter Link)
  • Shelly Pro (bezahlter Link) (z. B. Shelly Pro 1PM / Pro 2PM …)

Ziel:

  • Sender schaltet switch:0
  • Empfänger hört per LoRa
  • Empfänger ruft im LAN:
    • http://192.168.0.221/rpc/Switch.Set?id=0&on=true|false

LoRa-Grundprinzip: Kein Pairing, dafür gleiche Funkparameter

LoRa hier ist Broadcast: Ein Gerät sendet, alle Geräte mit den gleichen Funkparametern können empfangen. Es gibt kein „Kennenlernen“ wie bei Bluetooth.

Was wirklich zählen muss:

  • Region/Subband (EU / 865–868 MHz)
  • Frequenz/Channel (z. B. 868.000 MHz)
  • Bandwidth (typisch 125 kHz)
  • Spreading Factor (SF)

Spreading Factor verständlich erklärt (und warum SF12 oft viel zu viel ist)

Der Spreading Factor ist der große Hebel für Reichweite vs. Geschwindigkeit:

  • niedriger SF (z. B. SF7) → schnell, weniger „Sicherheitsnetz“
  • höherer SF (z. B. SF12) → extrem robust/weit, aber spürbar langsam

Für meinen Praxisfall (ca. 50 m + zwei Wände) war SF12 Overkill und hat die Latenz unnötig hochgezogen.

Empfehlung aus dem Test:

  • Starte mit SF8
  • wenn es noch stabil ist und du es schneller willst → SF7
  • wenn es Dropouts gibt → SF9

Damit bekommst du ein „klickt praktisch sofort“-Gefühl, ohne Reichweitenstress.

Sicherheit im Funkraum: Token + Whitelist + Replay-Schutz

Weil LoRa Broadcast ist, kommt der Schutz aus der Nachricht:

  • src: eindeutige Senderkennung (Whitelist)
  • token: gemeinsames Geheimnis (Shared Token)
  • seq: laufende Nummer gegen Replay (damit niemand alte Pakete wiederholt)

Das ist simpel, aber in der Praxis extrem wirksam.


Drei Use Cases im Überblick

Damit das Ganze wirklich praxisnah ist, hier drei Varianten – vom simplen Einstieg bis zum „LoRa-Gateway für Shelly Pro“.

  1. Use Case 1: Direkt spiegeln (ohne Token)
    • Minimaler Einstieg
    • Gut zum Testen, aber ohne Schutz
  2. Use Case 2: Direkt spiegeln (mit Token + Replay-Schutz)
    • Empfehlenswert für echte Installationen
    • Schützt vor „fremden“ Befehlen im gleichen Funkraum
  3. Use Case 3: Sender → Empfänger/Gateway → Shelly Pro (HTTP)
    • Shelly Pro kann kein LoRa → Empfänger wird zum Übersetzer

Use Case 1: Direkt spiegeln (ohne Token)

Das ist die „Hello World“-Variante: Der Sender sendet den Zustand, der Empfänger setzt seinen eigenen switch:0 entsprechend.

Script 1A: Sender (ohne Token)

// --- CONFIG ---
let CFG = {
  LORA_ID: 100,
  SWITCH_ID: 0,
  SRC: "wohnzimmer-master"
};

let seq = 0;

function sendObj(obj) {
  let msg = JSON.stringify(obj);
  Shelly.call("LoRa.SendBytes", { id: CFG.LORA_ID, data: btoa(msg) }, function (res, err, errmsg) {
    if (err) print("LoRa send error:", err, errmsg);
  });
}

function sendSwitchState(on) {
  seq++;
  sendObj({
    t: "mirror",
    src: CFG.SRC,
    id: CFG.SWITCH_ID,
    on: !!on,
    seq: seq,
    ts: Date.now()
  });
}

Shelly.addStatusHandler(function (ev) {
  if (ev.component !== ("switch:" + CFG.SWITCH_ID)) return;
  if (!ev.delta || typeof ev.delta.output === "undefined") return;

  let on = ev.delta.output;
  print("TX switch", CFG.SWITCH_ID, "=>", on);
  sendSwitchState(on);
}, null);

Timer.set(1500, false, function () {
  let st = Shelly.getComponentStatus("switch:" + CFG.SWITCH_ID);
  if (st && typeof st.output !== "undefined") {
    print("Initial sync =>", st.output);
    sendSwitchState(st.output);
  }
});

Script 1B: Empfänger (ohne Token)

let CFG = {
  SWITCH_ID: 0,
  ACCEPT_SRC: "wohnzimmer-master"
};

let lastSeqBySrc = {};

function applySwitch(on) {
  Shelly.call("Switch.Set", { id: CFG.SWITCH_ID, on: !!on }, function (res, err, msg) {
    if (err) print("Switch.Set error:", err, msg);
  });
}

Shelly.addEventHandler(function (ev) {
  let b64 = (ev && ev.info && ev.info.data) ? ev.info.data : (ev && ev.data ? ev.data : null);
  if (!b64) return;

  let txt, obj;
  try { txt = atob(b64); } catch (e) { return; }
  try { obj = JSON.parse(txt); } catch (e) { return; }

  if (obj.t !== "mirror") return;
  if (obj.src !== CFG.ACCEPT_SRC) return;
  if (obj.id !== CFG.SWITCH_ID) return;

  // kleiner Replay-Schutz (optional)
  let last = lastSeqBySrc[obj.src];
  if (typeof obj.seq === "number") {
    if (typeof last !== "undefined" && obj.seq <= last) return;
    lastSeqBySrc[obj.src] = obj.seq;
  }

  print("RX mirror:", obj.on);
  applySwitch(obj.on);
}, null);

Hinweis: Diese Variante ist super zum Einstieg. In einer Umgebung mit mehreren LoRa-Knoten würde ich aber Use Case 2 nehmen.

Use Case 2: Direkt spiegeln (mit Token + Replay-Schutz)

Hier kommt das „Mach’s privat“-Upgrade: Token + Whitelist + seq gegen Replay.

Script 2A: Sender (mit Token)

// --- CONFIG ---
let CFG = {
  LORA_ID: 100,
  SWITCH_ID: 0,
  SRC: "wohnzimmer-master",
  TOKEN: "d2c7b1f4c0a74f33b9e2b1b7a8f6c1f0"
};

let seq = 0;

function sendObj(obj) {
  let msg = JSON.stringify(obj);
  Shelly.call("LoRa.SendBytes", { id: CFG.LORA_ID, data: btoa(msg) }, function (res, err, errmsg) {
    if (err) print("LoRa send error:", err, errmsg);
  });
}

function sendSwitchState(on) {
  seq++;
  sendObj({
    t: "mirror",
    src: CFG.SRC,
    token: CFG.TOKEN,
    id: CFG.SWITCH_ID,
    on: !!on,
    seq: seq,
    ts: Date.now()
  });
}

Shelly.addStatusHandler(function (ev) {
  if (ev.component !== ("switch:" + CFG.SWITCH_ID)) return;
  if (!ev.delta || typeof ev.delta.output === "undefined") return;

  let on = ev.delta.output;
  print("TX switch", CFG.SWITCH_ID, "=>", on);
  sendSwitchState(on);
}, null);

Timer.set(1500, false, function () {
  let st = Shelly.getComponentStatus("switch:" + CFG.SWITCH_ID);
  if (st && typeof st.output !== "undefined") {
    print("Initial sync =>", st.output);
    sendSwitchState(st.output);
  }
});

Script 2B: Empfänger (mit Token)

let CFG = {
  SWITCH_ID: 0,
  ACCEPT_SRC: "wohnzimmer-master",
  TOKEN: "d2c7b1f4c0a74f33b9e2b1b7a8f6c1f0"
};

let lastSeqBySrc = {};

function applySwitch(on) {
  Shelly.call("Switch.Set", { id: CFG.SWITCH_ID, on: !!on }, function (res, err, msg) {
    if (err) print("Switch.Set error:", err, msg);
  });
}

Shelly.addEventHandler(function (ev) {
  let b64 = (ev && ev.info && ev.info.data) ? ev.info.data : (ev && ev.data ? ev.data : null);
  if (!b64) return;

  let txt, obj;
  try { txt = atob(b64); } catch (e) { return; }
  try { obj = JSON.parse(txt); } catch (e) { return; }

  if (obj.t !== "mirror") return;
  if (obj.src !== CFG.ACCEPT_SRC) return;
  if (obj.token !== CFG.TOKEN) return;
  if (obj.id !== CFG.SWITCH_ID) return;

  // Replay-Schutz
  let last = lastSeqBySrc[obj.src];
  if (typeof obj.seq !== "number") return;
  if (typeof last !== "undefined" && obj.seq <= last) return;
  lastSeqBySrc[obj.src] = obj.seq;

  print("RX ok:", obj.on, "seq:", obj.seq);
  applySwitch(obj.on);
}, null);

Use Case 3: Sender → Empfänger/Gateway → Shelly Pro (HTTP)

In dieser Variante bleibt der Sender wie in Use Case 2 (mit Token). Der Empfänger schaltet nicht seinen eigenen Ausgang, sondern ruft einen Shelly Pro im LAN per RPC auf.

Script 3A: Sender (identisch zu Use Case 2A)

Du kannst Script 2A 1:1 wiederverwenden.

Script 3B: Empfänger/Gateway → Shelly Pro

Ziel: http://192.168.0.221/Relais 0 schalten.

// --- CONFIG ---
let CFG = {
  ACCEPT_SRC: "wohnzimmer-master",
  TOKEN: "d2c7b1f4c0a74f33b9e2b1b7a8f6c1f0",

  PRO_IP: "192.168.0.221",
  PRO_SWITCH_ID: 0
};

let lastSeqBySrc = {};

function setShellyPro(on) {
  let url =
    "http://" + CFG.PRO_IP +
    "/rpc/Switch.Set?id=" + CFG.PRO_SWITCH_ID +
    "&on=" + (on ? "true" : "false");

  Shelly.call("HTTP.GET", { url: url }, function (res, err) {
    if (err) {
      print("HTTP error:", err);
    } else {
      print("Pro switched to", on);
    }
  });
}

Shelly.addEventHandler(function (ev) {
  let b64 = (ev && ev.info && ev.info.data) ? ev.info.data :
            (ev && ev.data ? ev.data : null);
  if (!b64) return;

  let txt, obj;
  try { txt = atob(b64); } catch (e) { return; }
  try { obj = JSON.parse(txt); } catch (e) { return; }

  if (obj.t !== "mirror") return;
  if (obj.src !== CFG.ACCEPT_SRC) return;
  if (obj.token !== CFG.TOKEN) return;

  let last = lastSeqBySrc[obj.src];
  if (typeof obj.seq !== "number") return;
  if (typeof last !== "undefined" && obj.seq <= last) return;
  lastSeqBySrc[obj.src] = obj.seq;

  print("RX valid → forward to Shelly Pro:", obj.on);
  setShellyPro(obj.on);
}, null);


Fazit

Damit hast du drei sauber abgestufte Wege, ein Shelly LoRa Script produktiv zu nutzen:

  • direkt spiegeln (simpel)
  • direkt spiegeln (sicher)
  • Gateway bauen (LoRa → LAN → Shelly Pro)

Und genau das macht LoRa im Smart Home so charmant: Es ist nicht nur Reichweite – es ist ein zusätzlicher, unabhängiger Kommunikationsweg.


FAQ zum Einsatz von Shelly LoRa

Kann ich mehrere Relais steuern?
Ja. Nutze id im Payload und mappe im Empfänger auf mehrere Ziele.

Was ist der beste Spreading Factor für Haus & Hof?
Meist SF7–SF9. SF12 ist für „ich will’s wirklich weit und durch viel Material“.

Kann ich statt HTTP auch MQTT nutzen?
Ja – das Gateway-Prinzip bleibt identisch.


Mehr Wissen rund um Shelly, LoRa und lokale Automatisierung

Wenn du tiefer ins Thema einsteigen möchtest, findest du auf prokrastinerd.de bereits viele passende Artikel rund um Shelly, lokale Automatisierung und Smart-Home-Grundlagen. Besonders gut ergänzend zu diesem Shelly LoRa Script Praxistest sind:

Diese Artikel helfen dabei, das gezeigte Setup sinnvoll einzuordnen und auf größere Smart-Home-Strukturen zu erweitern.

Eingesetzte Shelly-Hardware

Für diesen Shelly LoRa Script Praxistest kamen bewusst keine exotischen Spezialgeräte zum Einsatz, sondern Komponenten, die sich im Alltag bewährt haben:

  • Shelly LoRa Add-on*
    Grundlage für die Funkverbindung. Wird direkt auf kompatible Shelly-Geräte gesteckt und lässt sich vollständig lokal betreiben.
  • Shelly 1PM Gen3 (bezahlter Link)
    Ideal als Sender oder Empfänger, da Shelly Scripts und das LoRa Add-on sauber integriert sind.
  • Shelly Pro 4PM (bezahlter Link)
    Kommt ins Spiel, wenn Relais im Schaltschrank sitzen oder höhere Lasten geschaltet werden sollen. In diesem Aufbau wird der Shelly Pro per HTTP vom LoRa-Empfänger angesteuert.

Bewährtes Installationsmaterial aus der Praxis

Mindestens genauso wichtig wie die Elektronik ist die Art, wie sie installiert wird. Folgendes Material hat sich bei Smart-Home- und Shelly-Projekten immer wieder bewährt:

  • Stangenrohr / Installationsrohr*
    Ermöglicht saubere und nachrüstbare Leitungsführung – besonders sinnvoll, wenn Funklösungen später erweitert werden sollen.
  • AP-Abzweigdosen*
    Bieten Platz für Klemmen, Shellys oder spätere Erweiterungen und verhindern, dass alles dauerhaft in der Wand verschwindet.
  • WAGO-Klemmen (bezahlter Link)
    Schnell, sicher und übersichtlich. Für Umbauten und Erweiterungen deutlich angenehmer als klassische Schraubklemmen.
  • Abmantel- und Abisolierwerkzeug*
    Spart Zeit, schont Leiter und sorgt für saubere Kontakte – echtes Basiswerkzeug für jede Installation.

Die Kombination aus zuverlässiger Shelly-Hardware und solidem Installationsmaterial macht den Unterschied zwischen einem kurzfristigen Bastelprojekt und einer dauerhaft stabilen Smart-Home-Lösung.

Alle hier genannten Geräte, Komponenten und Werkzeuge wurden im Rahmen dieses Praxistests tatsächlich eingesetzt. Die Empfehlungen basieren also nicht auf Datenblättern oder Herstellerangaben, sondern auf meiner persönlichen Nutzung im Alltag.

Schreibe einen Kommentar