Skip to content

Commit 5317f1e

Browse files
author
Radoslav Sapundzhiev
committed
create lora wired dw send and recieve scripts
https://shellyusa.atlassian.net/browse/FW-1200
1 parent 05838d8 commit 5317f1e

File tree

3 files changed

+237
-0
lines changed

3 files changed

+237
-0
lines changed

lora/lora-wired-dw-state/README.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# LoRa Door/Window Sensor State Communication
2+
3+
## Short description
4+
5+
Scripts for sending door/window sensor state over LoRa between Shelly devices. The sender monitors an input connected to a DW sensor and transmits state changes (via status handler) and/or periodically (via timer) to a receiver, which displays the result in a virtual boolean component.
6+
7+
## Requirements
8+
9+
- Two Shelly devices with LoRa addons (e.g., Shelly i4DC Gen4, Shelly 1PM Gen4)
10+
- Door/Window sensor connected to an input on the sender device
11+
- Virtual boolean component configured on the receiver device (ID: 200)
12+
- AES 128-bit base64 encryption key for secure communication
13+
14+
### Generate AES Key
15+
16+
```bash
17+
openssl rand -base64 16
18+
```
19+
20+
## Configuration
21+
22+
### Sender Configuration (lora-wired-dw-sender.js)
23+
24+
| Parameter | Description | Default |
25+
|-----------|-------------|---------|
26+
| `loraComponentKey` | ID of the LoRa component instance | `"lora:100"` |
27+
| `tx_key_id` | Encryption key index [1,2,3] | `1` |
28+
| `lr_addr` | Recipient LoRa address (hex string) | `"000000BB"` |
29+
| `doorWindowComponent` | Input where DW sensor is connected | `"input:1"` |
30+
| `useStatusHandler` | Enable sending on state change | `true` |
31+
| `useTimer` | Enable periodic sending via timer | `true` |
32+
| `interval` | Timer interval in milliseconds | `3000` |
33+
34+
### Receiver Configuration (lora-wired-dw-reciever.js)
35+
36+
| Parameter | Description | Default |
37+
|-----------|-------------|---------|
38+
| `doorWindowComponent` | Input key from sender to match | `"input:1"` |
39+
| `doorWindowVirtualComponent` | Virtual component type | `"boolean"` |
40+
| `doorWindowVirtualComponentId` | Virtual component ID | `200` |
41+
| `lr_addr` | LoRa address | `"000000BB"` |
42+
| `key1` | Encryption key (same as sender's key1) | - |
43+
44+
**Note:** User LoRa calls must be set to `true` on both devices in LoRa transport layer config settings.
45+
46+
## Installation
47+
48+
1. Wire up your Shelly devices
49+
2. Attach LoRa addons to both devices
50+
3. Power up the devices
51+
4. In the web interface, go to Add-on submenu and enable LoRa add-on
52+
5. Enable "User LoRa calls" in LoRa transport layer config on both devices
53+
6. Configure matching encryption key (key1) on both sender and receiver
54+
55+
### Sender Setup
56+
57+
1. Create a script from `lora-wired-dw-sender.js` on the sender device
58+
2. Configure the `doorWindowComponent` to match your DW sensor input
59+
3. Set `lr_addr` to the receiver's LoRa address
60+
4. Save and run the script
61+
62+
### Receiver Setup
63+
64+
1. Create a virtual boolean component (ID: 200) on the receiver device
65+
2. Create a script from `lora-wired-dw-reciever.js` on the receiver device
66+
3. Configure `doorWindowComponent` to match the sender's configuration
67+
4. Save and run the script
68+
69+
## How It Works
70+
71+
### Sender
72+
73+
The sender supports two modes (can be enabled independently via CONFIG):
74+
75+
**Status Handler Mode** (`useStatusHandler: true`):
76+
- Listens for status changes on the configured input
77+
- When the DW sensor state changes (open/close), immediately sends a JSON message over LoRa
78+
79+
**Timer Mode** (`useTimer: true`):
80+
- Periodically sends the current state every `interval` milliseconds (default: 3000ms)
81+
- Useful as a heartbeat or to ensure receiver stays in sync
82+
83+
Message format:
84+
```json
85+
{"component": "input:1", "value": true}
86+
```
87+
88+
### Receiver
89+
90+
- Listens for LoRa events with `event.info.event === "user_rx"`
91+
- Decodes and parses the received message
92+
- Compares received value with current virtual component value
93+
- Only updates the virtual boolean component if the value has changed (reduces unnecessary RPC calls)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Script with event handler that listens for lora messages with "user" payload type
3+
* If doorWindowComponent is in the recieved data - show the result in virtual boolean component
4+
*/
5+
6+
const CONFIG = {
7+
//Input key on which the DW sensor is connected in the sender
8+
doorWindowComponent: "input:1",
9+
//Virtual component type boolean - to output the result
10+
doorWindowVirtualComponent: "boolean",
11+
//Virtual component id
12+
doorWindowVirtualComponentId: 200,
13+
};
14+
15+
Shelly.addEventHandler(function (event) {
16+
if (
17+
typeof event !== 'object' ||
18+
event.name !== 'lora' ||
19+
event.id !== 100 ||
20+
!event.info ||
21+
event.info.event !== "user_rx" ||
22+
!event.info.data
23+
) {
24+
return;
25+
}
26+
27+
const decodedMessage = atob(event.info.data);
28+
console.log("Message received: ", decodedMessage);
29+
30+
const data = JSON.parse(decodedMessage);
31+
const value = data.value;
32+
33+
if (data.component === CONFIG.doorWindowComponent) {
34+
const currentStatus = Shelly.getComponentStatus(
35+
CONFIG.doorWindowVirtualComponent + ":" + CONFIG.doorWindowVirtualComponentId
36+
);
37+
const currentValue = currentStatus.value;
38+
39+
if (value !== currentValue) {
40+
Shelly.call(
41+
CONFIG.doorWindowVirtualComponent + ".Set",
42+
{
43+
id: CONFIG.doorWindowVirtualComponentId,
44+
value: value
45+
}
46+
);
47+
}
48+
}
49+
});
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* Script with status handler that listens for DW sensor status changes - state: true/false
3+
* connected to one of the inptuts of I4DC Gen4
4+
* 1. Using the command "openssl rand -base64 16" generate AES 128 bits base64 key
5+
* 2. Put previously generated key to at least one of the keys of the sender and the reciever
6+
* 3. At least one of the keys in the receiver should be the same as tx_key of the sender
7+
*/
8+
9+
const CONFIG = {
10+
//ID of the LoRa component instance
11+
loraComponentKey: "lora:100",
12+
//The encryption key index, possible values: [1,2,3]
13+
//Optional - If not provided when calling the one from the config will be used for encryption
14+
tx_key_id: 1,
15+
//The address of recipient in LoRa network as hexadecimal string
16+
lr_addr: "000000BB",
17+
//Input key on which the DW sensor is connected
18+
doorWindowComponent: "input:1",
19+
//Enable/disable status handler (send on state change)
20+
useStatusHandler: true,
21+
//Enable/disable timer (send periodically)
22+
useTimer: true,
23+
//Timer interval in milliseconds
24+
interval: 3000,
25+
};
26+
27+
let statusHandler = null;
28+
let timerHandler = null;
29+
30+
function sendMessage(message) {
31+
Shelly.call(
32+
'LoRa.Send',
33+
{
34+
id: 100,
35+
lr_addr: CONFIG.lr_addr,
36+
tx_key_id: CONFIG.tx_key_id,
37+
data: btoa(message)
38+
},
39+
function (data, err, errmsg) {
40+
if (err) {
41+
console.log('Error:', err, errmsg);
42+
return;
43+
}
44+
}
45+
);
46+
}
47+
48+
function handleSensorStatus(eventData) {
49+
if (
50+
eventData.component !== "undefined" &&
51+
eventData.component.indexOf(CONFIG.doorWindowComponent) !== -1 &&
52+
eventData.delta !== "undefined" &&
53+
eventData.delta.state !== "undefined"
54+
) {
55+
const component = eventData.component;
56+
const state = eventData.delta.state;
57+
58+
console.log("eventData: ", JSON.stringify(eventData));
59+
60+
const data = {
61+
component: component,
62+
value: state
63+
};
64+
65+
sendMessage(JSON.stringify(data));
66+
}
67+
}
68+
69+
function send() {
70+
const status = Shelly.getComponentStatus(CONFIG.doorWindowComponent);
71+
const state = status.state;
72+
73+
const data = {
74+
component: CONFIG.doorWindowComponent,
75+
value: state
76+
};
77+
78+
sendMessage(JSON.stringify(data));
79+
}
80+
81+
// Status handler setup
82+
if (CONFIG.useStatusHandler) {
83+
if (statusHandler) {
84+
Shelly.removeStatusHandler(statusHandler);
85+
}
86+
statusHandler = Shelly.addStatusHandler(handleSensorStatus);
87+
}
88+
89+
// Timer setup
90+
if (CONFIG.useTimer) {
91+
if (timerHandler) {
92+
Timer.clear(timerHandler);
93+
}
94+
timerHandler = Timer.set(CONFIG.interval, true, send);
95+
}

0 commit comments

Comments
 (0)