Shelly LoRa Add-on

Gateway • REST-API (DDF)

Shelly
Download Signed DDF min. OS version: 10384-07
Device image

Mit diesem DDF können Shelly-Geräte mit LoRa-Addon eingebunden werden, wobei jeweils ein Ein- und Ausgang genutzt werden kann. Hinweis: Beachten Sie die regional geltenden Vorschriften zur LoRa-Nutzung. Ein Shelly mit LoRa-Addon wird im selben IP-Netzwerk wie der myGEKKO-Controller betrieben. Er dient als LoRa-Master-Gateway und kommuniziert mit weiteren Shelly-Geräten (Slaves) über LoRa.

Verbinden Sie sich über 192.168.33.1 mit dem WLAN-Access-Point des Shelly, verbinden Sie ihn anschließend mit dem WLAN und führen Sie bei Bedarf ein Firmware-Update durch. Getestet mit 2.0.0-beta1.

Shelly Master (Gateway im WLAN)

  • Script lora_master.js erstellen und aktivieren
  • Run on startup aktivieren
  • LoRa Add-on aktivieren
  • LoRa transport layer Device Address: 0001 (HEX)
  • User LoRa calls aktivieren
Skript-Code: lora_master.js
let lastByAddr = {};

function nowIso() {
  return new Date().toISOString();
}

function nowUnix() {
  return Math.floor(Date.now() / 1000);
}

// Shelly-safe Query Parser (KEIN decodeURIComponent!)
function parseQuery(qs) {
  let out = {};

  if (!qs) return out;

  let parts = qs.split("&");

  for (let i = 0; i < parts.length; i++) {
    let kv = parts[i].split("=");

    let key = kv[0];
    let val = kv.length > 1 ? kv[1] : "";

    if (key) out[key] = val;
  }

  return out;
}

Shelly.addEventHandler(function (ev) {
  if (!ev || !ev.info) return;

  let addr = ev.info.lr_addr || ev.info.addr || ev.info.sender;
  let data = ev.info.data;

  if (addr === undefined || data === undefined) return;

  addr = String(addr);

  try {
    data = JSON.parse(atob(String(data)));
  } catch (e) {
    try {
      data = atob(String(data));
    } catch (e2) {
      data = String(data);
    }
  }

  lastByAddr[addr] = {
    addr: addr,
    data: data,
    rssi: ev.info.rssi,
    snr: ev.info.snr,
    received: nowIso(),
    received_unix: nowUnix()
  };

  print("LoRa empfangen: " + addr);
});

HTTPServer.registerEndpoint("lora", function (req, res) {
  let query = parseQuery(req.query);
  let addr = query.addr;

  if (!addr) {
    res.code = 400;
    res.headers = [["Content-Type", "application/json"]];
    res.body = JSON.stringify({ error: "addr fehlt" });
    res.send();
    return;
  }

  let result = lastByAddr[String(addr)];

  if (!result) {
    res.code = 404;
    res.headers = [["Content-Type", "application/json"]];
    res.body = JSON.stringify({ error: "nicht gefunden", addr: addr });
    res.send();
    return;
  }

  res.code = 200;
  res.headers = [["Content-Type", "application/json"]];
  res.body = JSON.stringify(result);
  res.send();
});

print("Endpunkt aktiv: /script//lora?addr=");

Shelly Slave (LoRa Endgeräte)

  • Script lora_slave.js erstellen und aktivieren
  • Run on startup aktivieren
  • LoRa transport layer Device Address:
    • 0002 (HEX) – erstes Gerät
    • 0003 bis 000F (HEX) – weitere Slaves
  • User LoRa calls aktivieren
Skript-Code: lora_slave.js
let LORA_ID = 100;
let LORA_ADDR = "00000001";
let OUTPUT_ID = 0;

let di = 0;
let doo = 0;
let last = "";

function send(force) {
  // Status als Array senden
  let msg = JSON.stringify([di, doo]);

  if (!force && msg === last) return;
  last = msg;

  console.log("SENDE:", msg);

  Shelly.call("LoRa.Send", {
    id: LORA_ID,
    lr_addr: LORA_ADDR,
    data: btoa(msg)
  }, function (res, err, msg) {
    if (err !== 0) {
      console.log("LoRa Fehler:", err, msg);
    }
  });
}

function startPeriodicSend() {
  // alle 15 Minuten ein Status-Heartbeat
  Timer.set(15 * 60 * 1000, true, function () {
    send(true);
  });
}

function handleCommand(rawPayload) {
  let payload = rawPayload;

  // Falls data base64-kodiert ankommt, dekodieren
  try {
    payload = atob(rawPayload);
  } catch (e) {
    // wenn nicht base64, dann rawPayload direkt verwenden
  }

  console.log("LoRa RX raw:", rawPayload, "decoded:", payload);

  if (payload === "1" || payload === "ON" || payload === "true") {
    Shelly.call("Switch.Set", { id: OUTPUT_ID, on: true }, function (res, err, msg) {
      console.log("Switch.Set ON -> err:", err, "msg:", msg);
    });
  } else if (payload === "0" || payload === "OFF" || payload === "false") {
    Shelly.call("Switch.Set", { id: OUTPUT_ID, on: false }, function (res, err, msg) {
      console.log("Switch.Set OFF -> err:", err, "msg:", msg);
    });
  } else {
    console.log("Unbekannter Payload:", payload);
  }
}

Shelly.addEventHandler(function (ev) {
  if (ev.name !== "lora") return;
  if (!ev.info) return;

  if (ev.info.component !== "lora:100") return;
  if (ev.info.event !== "user_rx") return;

  handleCommand(ev.info.data);
});

Shelly.addStatusHandler(function (e) {
  if (!e || !e.component || !e.delta) return;

  if (e.component === "input:0" && e.delta.state !== undefined) {
    di = e.delta.state ? 1 : 0;
    send(false);
  }

  if (e.component === "switch:0" && e.delta.output !== undefined) {
    doo = e.delta.output ? 1 : 0;
    send(false);
  }
});

// einmaliger zufälliger Startversatz
var startDelay = Math.floor(Math.random() * 15 * 60 * 1000);
console.log("Start in", startDelay, "ms");

Timer.set(startDelay, false, function () {
  send(true);
  startPeriodicSend();
});

console.log("LoRa Kombi-Skript gestartet");

DeviceStation

  • Domain: http://<IP_Shelly_Master>
  • Slave-Adressen: 1,2, ... ,15

System „Gerät“

  • Je Shelly einen Baustein anlegen und Dashboard wählen
  • Als Widgets auf Startseite oder Räume/Bereiche verknüpfen

System „Licht“ oder „Steckdose“

  • Schaltausgang: SET Output
  • Rückmeldung: State Output
Manufacturer Type Protocol Model Version ID
Shelly Gateway REST-API (DDF) 2 1 0x0D00002D00020100
No documents.