Hallo,
ich bin nach zahlreichen Stunden der Verzweiflung, in meinem Fall zu einer Lösung gekommen.
Hier eine Vorlage zum Schalten entfernter Shelly´s inklusive Rückmeldung.
Die Scripte sind so ausgelegt, dass alles über die Shelly App bedient werden kann. Die Rückmeldung erfolgt über Text (on/off). Bei Übertragungsfehlern wechselt dieser auf "Fehler".
Zunächst muss man auf dem Sender für jeden Empfänger einen Boolean fürs Umschalten und einen Text für die Rückmeldung in den virtuellen Komponenten anlegen.
Zusätzlich noch einen VButton anlegen für die manuelle Statusabfrage nach Übertragungsfehlern (am besten id 200, ansonsten im Senderscript ändern).
Im Senderscript eine Sender id bei Device id festlegen. Bei Receivers die id´s der Empfänger eingeben (müssen identisch mit den Boolean und Text id´s sein, Anzahl der Empfänger bei Bedarf erweitern oder verringern).
Die Scripte Reagieren jeweils nur wenn in beiden die gleiche Sender id eingetragen ist.
// ======================= sender.js =======================
/**
* Shelly 1 Gen3 LoRa Sender (ID = 250)
* - CMD via boolean:<ID>
* - Status via text:<ID>
* - Status-Request via button:200
* - Wichtige print-Ausgaben für Debugging
*/
// --- Konfiguration ---
var DEVICE_ID = 250; // anpassen
var RECEIVERS = [200, 201, 202]; // anpassen
var BUTTON_STATUS = 'button:200'; // evtl anpassen
var CMD_TIMEOUT = 10000;
var REQ_DELAY = 1000;
var REQ_TIMEOUT = 10000;
// --- Zustand ---
var seqCounter = 0;
var pending = {};
var statusReqIdx = 0;
var statusReqActive = false;
// --- Helfer ---
function crc8(buf) {
var c = 0;
for (var i = 0; i < buf.length; i++) c ^= buf[i];
return c & 0xFF;
}
function makePacket(type, id, state, seq) {
var hdr = ((type & 3) << 6) | ((state ? 1 : 0) << 5) | ((seq & 1) << 4);
var arr = [hdr, id, seq];
arr.push(crc8(arr));
return arr;
}
function sendPacket(pkt, callback) {
var raw = String.fromCharCode.apply(null, pkt);
var data = btoa(raw);
print('📤 Sende LoRa:', pkt);
Shelly.call('Lora.SendBytes', {id:100, data:data}, function(res) {
print('📬 LoRa.SendBytes Rückmeldung:', JSON.stringify(res));
if (callback) callback(res);
});
}
function setText(id, text) {
print('📝 Setze Text:', id, text);
Shelly.call('text.set', {id: id, value: text}, function(res) {
print('📝 text.set Rückmeldung:', JSON.stringify(res));
});
}
function clearPending(id) {
if (pending[id] && pending[id].timer) {
Timer.clear(pending[id].timer);
print('⏹️ Timer gelöscht für', id);
}
delete pending[id];
}
// --- CMD bei Boolean-Änderung ---
Shelly.addStatusHandler(function(evt) {
print('🔔 Boolean event:', evt.component, 'delta:', JSON.stringify(evt.delta));
var comp = evt.component;
if (typeof comp === 'string' && comp.indexOf('boolean:') === 0
&& evt.delta && typeof evt.delta.value === 'boolean') {
var id = parseInt(comp.split(':')[1], 10);
var state = evt.delta.value;
seqCounter ^= 1;
var seq = seqCounter;
print('🔔 Boolean-Event:', comp, '→', state);
clearPending(id);
pending[id] = {type:0, seq:seq, state:state};
var pkt = makePacket(0, id, state ? 1 : 0, seq);
print('▶️ CMD senden an', id, 'Pkt:', pkt);
sendPacket(pkt, function() {
pending[id].timer = Timer.set(CMD_TIMEOUT, false, function() {
print('⚠️ CMD-Timeout für', id);
setText(id, 'Fehler');
delete pending[id];
});
});
}
});
// --- Status-Request per virtuellem Button ---
function sendStatusRequest() {
if (statusReqIdx >= RECEIVERS.length) {
statusReqActive = false;
return;
}
var id = RECEIVERS[statusReqIdx++];
seqCounter ^= 1;
var seq = seqCounter;
print('▶️ REQ senden an', id, 'Seq:', seq);
pending[id] = {type:1, seq: seq};
var pkt = makePacket(1, id, 0, seq);
sendPacket(pkt);
pending[id].timer = Timer.set(REQ_TIMEOUT, false, function() {
print('⚠️ REQ-Timeout für', id);
setText(id, 'Fehler');
delete pending[id];
Timer.set(REQ_DELAY, false, sendStatusRequest);
});
}
Shelly.addEventHandler(function(evt) {
if (evt.component === BUTTON_STATUS && evt.info && evt.info.event === 'single_push') {
print('🔘 Button gedrückt: Status-Request');
RECEIVERS.forEach(function(id) { clearPending(id); });
statusReqIdx = 0;
statusReqActive = true;
sendStatusRequest();
}
});
// --- Empfang + ACK/REPORT ---
Shelly.addEventHandler(function(evt) {
if (evt.name !== 'lora' || !(evt.info && evt.info.data)) return;
var raw = atob(evt.info.data);
var buf = [];
for (var i = 0; i < raw.length; i++) buf.push(raw.charCodeAt(i));
print('📥 Empfangen:', buf);
if (buf.length !== 4) return;
var hdr = buf[0], id = buf[1], seq = buf[2], crc = buf[3];
if (crc !== crc8([hdr, id, seq])) {
print('❌ CRC falsch');
return;
}
var type = (hdr >> 6) & 3;
var state = ((hdr >> 5) & 1) === 1;
print('🔍 Geparst Type=' + type + ' ID=' + id + ' State=' + state + ' Seq=' + seq);
// ACK handling
var entry = pending[id];
if (type === 3 && entry && entry.seq === seq) {
print('✅ ACK erhalten für', id);
if (entry.timer) Timer.clear(entry.timer);
if (entry.type === 0) {
setText(id, entry.state ? 'ON' : 'OFF');
}
delete pending[id];
return;
}
// REPORT handling
if (type === 2) {
print('📊 REPORT erhalten von', id, 'State=' + state);
if (pending[id] && pending[id].timer) Timer.clear(pending[id].timer);
setText(id, state ? 'ON' : 'OFF');
delete pending[id];
// Nach erfolgreichem REPORT nächsten REQ senden, falls aktiv
if (statusReqActive) {
Timer.set(REQ_DELAY, false, sendStatusRequest);
}
return;
}
});
Alles anzeigen
Beim Empfängerscript jeweils die Receiver ID anpassen und ebenfalls den Sender.
// ======================= receiver.js =======================
/**
* Shelly 1 Gen3 LoRa Empfänger (ID = 200/201/202)
*/
var RECEIVER_ID = 200; // anpassen
var SENDER_ID = 250; // anpassen
// Helper-Funktionen aus sender.js
function crc8(buf) {
var c = 0; for (var i = 0; i < buf.length; i++) c ^= buf[i];
return c & 0xFF;
}
function makePacket(type, id, state, seq) {
var hdr = ((type & 3) << 6) | ((state ? 1 : 0) << 5) | ((seq & 1) << 4);
var arr = [hdr, id, seq]; arr.push(crc8(arr)); return arr;
}
function sendPacketR(arr) {
var raw = String.fromCharCode.apply(null, arr);
var data = btoa(raw);
print('📤 RX→TX LoRa:', arr);
Shelly.call('Lora.SendBytes', {id:100, data:data}, function(res) {
print('📬 LoRa.SendBytes Rückmeldung:', JSON.stringify(res));
});
}
Shelly.addEventHandler(function(evt) {
if (evt.name !== 'lora' || !(evt.info && evt.info.data)) return;
var raw = atob(evt.info.data), buf = [];
for (var i = 0; i < raw.length; i++) buf.push(raw.charCodeAt(i));
print('📥 Empfangen (Receiver):', buf);
if (buf.length !== 4) return;
var hdr = buf[0],
id = buf[1],
seq = buf[2],
crc = buf[3];
if (crc !== crc8([hdr, id, seq])) {
print('❌ Receiver CRC falsch');
return;
}
var type = (hdr >> 6) & 3;
var state = ((hdr >> 5) & 1) === 1;
print('🔍 Receiver geparst Type=' + type + ' ID=' + id + ' State=' + state + ' Seq=' + seq);
if (type === 0 && id === RECEIVER_ID) {
print('⚙️ Receiver CMD für mich:', state);
Shelly.call('Switch.Set', {id:0, on: state});
// ACK sofort senden
var ack = makePacket(3, SENDER_ID, 0, seq);
sendPacketR(ack);
// Fester Verzögerungswert für REPORT
var delayMs = 1500;
print('⏱️ Receiver REPORT in', delayMs, 'ms');
Timer.set(delayMs, false, function() {
var rpt = makePacket(2, RECEIVER_ID, state ? 1 : 0, seq);
print('📤 Receiver REPORT senden:', rpt);
sendPacketR(rpt);
});
}
if (type === 1 && id === RECEIVER_ID) {
print('🔍 Receiver REQ für mich');
// ACK sofort senden
var ack = makePacket(3, SENDER_ID, 0, seq);
sendPacketR(ack);
// Fester Verzögerungswert für STATUS-REPORT
var delayMs = 1000;
print('⏱️ Receiver Status-Report in', delayMs, 'ms');
Timer.set(delayMs, false, function() {
Shelly.call('Switch.GetStatus', {id:0}, function(res) {
var st = !!res.output;
print('⚙️ Receiver Status:', st);
var rpt = makePacket(2, RECEIVER_ID, st ? 1 : 0, seq);
print('📤 Receiver REPORT senden:', rpt);
sendPacketR(rpt);
});
});
}
});
Alles anzeigen
Bei mir hat das so bisher Reibungslos geklappt. Wichtig ist, nach dem Umschalten der Booleans kurz zu warten, bis die Rückmeldung kommt. Ansonsten Spammen sich Sender und Empfänger gegenseitig zu und es kommt der Status Fehler.