1// 🔐 --- Konfiguration & AES-Verschlüsselung ---
2const aesKey = 'dd469421e5f4089a1418ea24ba37c61bdd469421e5f4089a1418ea24ba37c61b';
3const CHECKSUM_SIZE = 4;
4let lastRemoteState = null; // Letzter per LoRa empfangener Zustand
5
6// 📦 Nachricht verschlüsseln
7function encryptMessage(msg, keyHex) {
8 function fromHex(hex) {
9 const arr = new ArrayBuffer(hex.length / 2);
10 for (let i = 0; i < hex.length; i += 2) {
11 arr[i / 2] = parseInt(hex.substr(i, 2), 16);
12 }
13 return arr;
14 }
15
16 function padRight(msg, blockSize) {
17 const paddingSize = (blockSize - msg.length % blockSize) % blockSize;
18 for (let i = 0; i < paddingSize; i++) msg += ' ';
19 return msg;
20 }
21
22 msg = msg.trim();
23 const formattedMsg = padRight(msg, 16);
24 const key = fromHex(keyHex);
25 return AES.encrypt(formattedMsg, key, { mode: 'ECB' });
26}
27
28// ✔️ Prüfsumme erzeugen
29function generateChecksum(msg) {
30 let checksum = 0;
31 for (let i = 0; i < msg.length; i++) {
32 checksum ^= msg.charCodeAt(i);
33 }
34 let hexChecksum = checksum.toString(16);
35 while (hexChecksum.length < CHECKSUM_SIZE) {
36 hexChecksum = '0' + hexChecksum;
37 }
38 return hexChecksum.slice(-CHECKSUM_SIZE);
39}
40
41// 📡 Nachricht senden + optional Relais lokal schalten
42function sendMessage(message) {
43 const checkSumMessage = generateChecksum(message) + message;
44 const encryptedMessage = encryptMessage(checkSumMessage, aesKey);
45
46 Shelly.call(
47 'Lora.SendBytes',
48 {
49 id: 100, // 🛰 Zielgerät-ID
50 data: btoa(encryptedMessage)
51 },
52 function (_, err_code, err_msg) {
53 if (err_code !== 0) {
54 print('LoRa send error:', err_code, err_msg);
55 } else {
56 print('LoRa message sent:', message);
57 }
58 }
59 );
60
61 // 🟢 Lokales Relais auf Wunsch mitsteuern
62 if (message === "100") {
63 Shelly.call("Switch.Set", { id: 0, on: true });
64 print("Lokales Relais: EIN");
65 } else if (message === "000") {
66 Shelly.call("Switch.Set", { id: 0, on: false });
67 print("Lokales Relais: AUS");
68 }
69}
70
71// 🔁 Lokalen Schaltzustand überwachen und senden
72Shelly.addStatusHandler(function (e) {
73 if (e.component === "switch:0" && e.delta && typeof e.delta.output === "boolean") {
74 const isOn = e.delta.output;
75
76 if (lastRemoteState !== null) {
77 print("[LoRa] Änderung durch entfernte Nachricht – kein Re-Senden");
78 lastRemoteState = null;
79 return;
80 }
81
82 const message = isOn ? "100" : "000";
83 sendMessage(message);
84 }
85});
86
87// 📥 LoRa-Nachrichten entschlüsseln
88function decryptMessage(buffer, keyHex) {
89 function fromHex(hex) {
90 const arr = new ArrayBuffer(hex.length / 2);
91 for (let i = 0; i < hex.length; i += 2) {
92 arr[i / 2] = parseInt(hex.substr(i, 2), 16);
93 }
94 return arr;
95 }
96
97 function hex2a(hex) {
98 let str = '';
99 for (let i = 0; i < hex.length; i += 2) {
100 str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
101 }
102 return str;
103 }
104
105 function toHex(buffer) {
106 let s = '';
107 for (let i = 0; i < buffer.length; i++) {
108 s += (256 + buffer[i]).toString(16).substr(-2);
109 }
110 return s;
111 }
112
113 const key = fromHex(keyHex);
114 const decrypted = AES.decrypt(buffer, key, { mode: 'ECB' });
115 if (!decrypted || decrypted.byteLength === 0) return;
116
117 const hex = toHex(decrypted);
118 return hex2a(hex).trim();
119}
120
121// 🛡 Prüfsumme prüfen
122function verifyMessage(message) {
123 if (!message || message.length < CHECKSUM_SIZE + 1) return null;
124 const receivedChecksum = message.slice(0, CHECKSUM_SIZE);
125 const payload = message.slice(CHECKSUM_SIZE);
126 const expectedChecksum = generateChecksum(payload);
127 return (receivedChecksum === expectedChecksum) ? payload : null;
128}
129
130// 🎯 Event-Handler für LoRa-Empfang
131Shelly.addEventHandler(function (event) {
132 if (!event || event.name !== 'lora' || !event.info || !event.info.data) return;
133
134 const encryptedMsg = atob(event.info.data);
135 const decrypted = decryptMessage(encryptedMsg, aesKey);
136 if (!decrypted) return;
137
138 const clean = verifyMessage(decrypted);
139 if (!clean) {
140 print("[LoRa] Checksum mismatch");
141 return;
142 }
143
144 print("[LoRa] Received:", clean);
145
146 if (clean === "100") {
147 lastRemoteState = true;
148 Shelly.call("Switch.Set", { id: 0, on: true });
149 } else if (clean === "000") {
150 lastRemoteState = false;
151 Shelly.call("Switch.Set", { id: 0, on: false });
152 } else {
153 print("[LoRa] Unknown command:", clean);
154 }
155});