Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions lora/lora-wired-dw-state/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# LoRa Door/Window Sensor State Communication

## Short description

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.

## Requirements

- Two Shelly devices with LoRa addons (e.g., Shelly i4DC Gen4, Shelly 1PM Gen4)
- Door/Window sensor connected to an input on the sender device
- Virtual boolean component configured on the receiver device (ID: 200)
- AES 128-bit base64 encryption key for secure communication

### Generate AES Key

```bash
openssl rand -base64 16
```

## Configuration

### Sender Configuration (lora-wired-dw-sender.js)

| Parameter | Description | Default |
|-----------|-------------|---------|
| `loraComponentKey` | ID of the LoRa component instance | `"lora:100"` |
| `tx_key_id` | Encryption key index [1,2,3] | `1` |
| `lr_addr` | Recipient LoRa address (hex string) | `"000000BB"` |
| `doorWindowComponent` | Input where DW sensor is connected | `"input:1"` |
| `useStatusHandler` | Enable sending on state change | `true` |
| `useTimer` | Enable periodic sending via timer | `true` |
| `interval` | Timer interval in milliseconds | `3000` |

### Receiver Configuration (lora-wired-dw-reciever.js)

| Parameter | Description | Default |
|-----------|-------------|---------|
| `doorWindowComponent` | Input key from sender to match | `"input:1"` |
| `doorWindowVirtualComponent` | Virtual component type | `"boolean"` |
| `doorWindowVirtualComponentId` | Virtual component ID | `200` |
| `lr_addr` | LoRa address | `"000000BB"` |
| `key1` | Encryption key (same as sender's key1) | - |

**Note:** User LoRa calls must be set to `true` on both devices in LoRa transport layer config settings.

## Installation

1. Wire up your Shelly devices
2. Attach LoRa addons to both devices
3. Power up the devices
4. In the web interface, go to Add-on submenu and enable LoRa add-on
5. Enable "User LoRa calls" in LoRa transport layer config on both devices
6. Configure matching encryption key (key1) on both sender and receiver

### Sender Setup

1. Create a script from `lora-wired-dw-sender.js` on the sender device
2. Configure the `doorWindowComponent` to match your DW sensor input
3. Set `lr_addr` to the receiver's LoRa address
4. Save and run the script

### Receiver Setup

1. Create a virtual boolean component (ID: 200) on the receiver device
2. Create a script from `lora-wired-dw-reciever.js` on the receiver device
3. Configure `doorWindowComponent` to match the sender's configuration
4. Save and run the script

## How It Works

### Sender

The sender supports two modes (can be enabled independently via CONFIG):

**Status Handler Mode** (`useStatusHandler: true`):
- Listens for status changes on the configured input
- When the DW sensor state changes (open/close), immediately sends a JSON message over LoRa

**Timer Mode** (`useTimer: true`):
- Periodically sends the current state every `interval` milliseconds (default: 3000ms)
- Useful as a heartbeat or to ensure receiver stays in sync

Message format:
```json
{"component": "input:1", "value": true}
```

### Receiver

- Listens for LoRa events with `event.info.event === "user_rx"`
- Decodes and parses the received message
- Compares received value with current virtual component value
- Only updates the virtual boolean component if the value has changed (reduces unnecessary RPC calls)
49 changes: 49 additions & 0 deletions lora/lora-wired-dw-state/lora-wired-dw-reciever.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Script with event handler that listens for lora messages with "user" payload type
* If doorWindowComponent is in the recieved data - show the result in virtual boolean component
*/

const CONFIG = {
//Input key on which the DW sensor is connected in the sender
doorWindowComponent: "input:1",
//Virtual component type boolean - to output the result
doorWindowVirtualComponent: "boolean",
//Virtual component id
doorWindowVirtualComponentId: 200,
};

Shelly.addEventHandler(function (event) {
if (
typeof event !== 'object' ||
event.name !== 'lora' ||
event.id !== 100 ||
!event.info ||
event.info.event !== "user_rx" ||
!event.info.data
) {
return;
}

const decodedMessage = atob(event.info.data);
console.log("Message received: ", decodedMessage);

const data = JSON.parse(decodedMessage);
const value = data.value;

if (data.component === CONFIG.doorWindowComponent) {
const currentStatus = Shelly.getComponentStatus(
CONFIG.doorWindowVirtualComponent + ":" + CONFIG.doorWindowVirtualComponentId
);
const currentValue = currentStatus.value;

if (value !== currentValue) {
Shelly.call(
CONFIG.doorWindowVirtualComponent + ".Set",
{
id: CONFIG.doorWindowVirtualComponentId,
value: value
}
);
}
}
});
95 changes: 95 additions & 0 deletions lora/lora-wired-dw-state/lora-wired-dw-sender.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* Script with status handler that listens for DW sensor status changes - state: true/false
* connected to one of the inptuts of I4DC Gen4
* 1. Using the command "openssl rand -base64 16" generate AES 128 bits base64 key
* 2. Put previously generated key to at least one of the keys of the sender and the reciever
* 3. At least one of the keys in the receiver should be the same as tx_key of the sender
*/

const CONFIG = {
//ID of the LoRa component instance
loraComponentKey: "lora:100",
//The encryption key index, possible values: [1,2,3]
//Optional - If not provided when calling the one from the config will be used for encryption
tx_key_id: 1,
//The address of recipient in LoRa network as hexadecimal string
lr_addr: "000000BB",
//Input key on which the DW sensor is connected
doorWindowComponent: "input:1",
//Enable/disable status handler (send on state change)
useStatusHandler: true,
//Enable/disable timer (send periodically)
useTimer: true,
//Timer interval in milliseconds
interval: 3000,
};

let statusHandler = null;
let timerHandler = null;

function sendMessage(message) {
Shelly.call(
'LoRa.Send',
{
id: 100,
lr_addr: CONFIG.lr_addr,
tx_key_id: CONFIG.tx_key_id,
data: btoa(message)
},
function (data, err, errmsg) {
if (err) {
console.log('Error:', err, errmsg);
return;
}
}
);
}

function handleSensorStatus(eventData) {
if (
eventData.component !== "undefined" &&
eventData.component.indexOf(CONFIG.doorWindowComponent) !== -1 &&
eventData.delta !== "undefined" &&
eventData.delta.state !== "undefined"
) {
const component = eventData.component;
const state = eventData.delta.state;

console.log("eventData: ", JSON.stringify(eventData));

const data = {
component: component,
value: state
};

sendMessage(JSON.stringify(data));
}
}

function send() {
const status = Shelly.getComponentStatus(CONFIG.doorWindowComponent);
const state = status.state;

const data = {
component: CONFIG.doorWindowComponent,
value: state
};

sendMessage(JSON.stringify(data));
}

// Status handler setup
if (CONFIG.useStatusHandler) {
if (statusHandler) {
Shelly.removeStatusHandler(statusHandler);
}
statusHandler = Shelly.addStatusHandler(handleSensorStatus);
}

// Timer setup
if (CONFIG.useTimer) {
if (timerHandler) {
Timer.clear(timerHandler);
}
timerHandler = Timer.set(CONFIG.interval, true, send);
}