Shelly LoRa Add-on

Gateway • REST-API (DDF)

Shelly
Scarica DDF firmato Versione min. OS: 10384-07
Device image

Con questo DDF è possibile integrare i dispositivi Shelly con l'add-on LoRa, consentendo l'uso di un ingresso e un'uscita per ciascuno. Nota: rispettare le normative locali vigenti sull'uso di LoRa. Uno Shelly con add-on LoRa viene gestito nella stessa rete IP del controller myGEKKO. Funge da gateway master LoRa e comunica con altri dispositivi Shelly (slave) tramite LoRa.

Connettersi all'access point Wi-Fi dello Shelly tramite 192.168.33.1, quindi collegarlo alla rete Wi-Fi ed eseguire un aggiornamento del firmware se necessario. Testato con 2.0.0-beta1, add-on 2.1.1.

Shelly Master (Gateway nella rete Wi-Fi)

  • Creare e attivare lo script lora_master.js
  • Abilitare "Run on startup"
  • Attivare l'add-on LoRa, la configurazione deve essere identica su tutti gli Shelly!
  • LoRa transport layer Device Address: 0001 (HEX)
  • Generare e annotare la chiave crittografica (Cryptography-Key)
  • Abilitare "User LoRa calls"
Codice script: lora_master.js
let lastByAddr = {};

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

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

// Shelly-safe Query Parser (NO 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 ricevuto: " + 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 mancante" });
    res.send();
    return;
  }

  let result = lastByAddr[String(addr)];

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

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

print("Endpoint attivo: /script//lora?addr=");

Shelly Slave (Dispositivi terminali LoRa)

  • Creare e attivare lo script lora_slave.js
  • Abilitare "Run on startup"
  • LoRa transport layer Device Address:
    • 0002 (HEX) – primo dispositivo
    • 0003 fino a 000F (HEX) – ulteriori slave
  • Inserire la chiave crittografica (Cryptography-Key) del Master
  • Abilitare "User LoRa calls"
Codice script: 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) {
  // Invia lo stato come array
  let msg = JSON.stringify([di, doo]);

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

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

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

function startPeriodicSend() {
  // un heartbeat di stato ogni 15 minuti
  Timer.set(15 * 60 * 1000, true, function () {
    send(true);
  });
}

function handleCommand(rawPayload) {
  let payload = rawPayload;

  // Se i dati arrivano codificati in base64, decodificali
  try {
    payload = atob(rawPayload);
  } catch (e) {
    // se non è in base64, usa direttamente rawPayload
  }

  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("Payload sconosciuto:", 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);
  }
});

// ritardo di avvio casuale una tantum
var startDelay = Math.floor(Math.random() * 15 * 60 * 1000);
console.log("Avvio in", startDelay, "ms");

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

console.log("Script combinato LoRa avviato");

DeviceStation

  • Domain: http://<IP_Shelly_Master>
  • Indirizzi slave: 1,2, ... ,15

Sistema "Dispositivo"

  • Creare un blocco per ogni Shelly e selezionare Dashboard
  • Collegare come widget sulla pagina iniziale o nelle stanze/aree

Sistema "Luce" o "Presa"

  • Uscita di commutazione: SET Output
  • Feedback: State Output
To Do: Lo script slave attuale non utilizza una coda di invio. In caso di rapidi cambiamenti di stato, possono essere avviate più chiamate LoRa.Send contemporaneamente, il che può causare l'errore Shelly Too many calls in progress. Si consiglia di implementare un flag di occupato (busy flag) o una logica di coda/debounce, in modo che sia attivo un solo processo di invio LoRa alla volta.
Codice script con coda e input, output sequenziali: lora_slave_queue.js

let LORA_ID = 100;
let LORA_ADDR = "00000001";
let OUTPUT_ID = 0;

let LORA_SEND_GAP_MS = 2000;

let di = 0;
let doo = 0;

let loraBusy = false;
let sendQueue = [];
let queuePos = 0;

let last                        
Produttore Tipo Protocollo Modello Versione ID
Shelly Gateway REST-API (DDF) 2 1 0x0D00002D00020100
Nessun documento.
ID Nome Unita Tipo Direzione
0 Slave
1 State Input
2 State Output
3 RSSI
4 Received Unix
5 Received
6 Message Age
100 SET Output
200 Quality
*2026-06-15 13:30:01 (hw) ******************************
Last Commit: ac75e22f | 2026-06-10 07:25:55