Local Network Messaging (LNM) — Setup Guide
Local Network Messaging (LNM) lets Shelly devices exchange data directly over the local network using UDP multicast, with no cloud and no internet connection required. A device can transmit the status of its components, send a command to a group of devices at once, and react to messages from other devices on the same network.
LNM is a dynamic component: instances are created at runtime and do not exist by default. Each device supports up to 5 instances (IDs 200–299, keys lnm:200, lnm:201, …). Each instance is one independent group, with its own multicast address, transmit list, and settings.
This guide covers setting up communication between two devices, configuring TX (Transmit) / RX (Receive) / RPC, sending group commands with LNM.Call, building automations in the web interface, and using LNM in scripts. You can configure LNM either from the device's web interface, in its Local Network Messaging section, or over RPC. The RPC examples below are shown as HTTP GET requests (with curl and mos alternatives); they use 192.168.33.1, the device's address in AP mode — replace it with your device's IP on your network.
For the complete API reference — all RPC methods, the configuration object, and the binary wire format — see the LNM component reference.
LNM is available as a preview. The API may change in future firmware.
The complete LNM functionality — sending component status, group commands (LNM.Call), and webhook triggers — is available as of firmware 2.0.0-beta3. See the API docs changelog for the full list of changes in this release.
Multicast address and port
A group is identified by a multicast address and a port, written together, for example 239.255.0.1:3333. Two devices communicate only when both use the same address and the same port.
A multicast address identifies a group, not a single device, so its numbers do not map to a network and a host the way a normal IP does. Only the first number carries fixed meaning: a value of 224–239 marks the address as multicast. The remaining numbers are the group identifier you choose, and the port separates applications that share the same group. In practice you do not interpret the individual numbers — you pick one address from the recommended range and use the same address and port on every device in the group.
Recommended addresses
Use an address in the 239.255.x.y range. A reliable default is:
239.255.0.1:3333
If you run several LNM instances, give each its own address. For example:
239.255.0.1:3333
239.255.0.2:3333
239.255.0.3:3333
239.255.0.4:3333
239.255.0.5:3333
Port: 3333 is a suitable default. Any port from 1024–65535 works, provided it is identical on every device in the group.
The full multicast space (224.0.0.0–239.255.255.255) is split by IANA (the Internet Assigned Numbers Authority) into blocks, most of which are reserved for networking protocols and internet-wide use. Only the 239.0.0.0/8 block is set aside for private use inside your own network — it is the multicast counterpart of the 192.168.x.x addresses on your LAN, and consumer routers handle it predictably.
Within 239.0.0.0/8, the 239.255.0.0/16 range is the site-local scope — the smallest scope, meant never to leave a single network, and the safest choice for a home or office LAN. Any 239.255.x.y address belongs to it (the 255 in the second position marks this scope). The wider 239.192.x.x–239.251.x.x range is also private (the organization-local scope) and works too, for addresses outside the site-local range.
The remaining ranges should be avoided, because they either collide with low-level hardware addressing or are already used by other services:
| Range | Use for LNM? | Reason |
|---|---|---|
239.255.x.y | Recommended | Site-local; the smallest private scope, predictable on home networks |
239.192.x.x–239.251.x.x | Also works | Organization-local private range — use only if you need addresses outside the site-local range |
239.0.0.x, 239.128.0.x | Avoid | Collide with the low-level hardware addresses of the control range; many switches flood or mishandle them |
239.255.255.x | Avoid | Reserved for other services (e.g. SSDP/UPnP) |
224.0.0.x | Avoid | Reserved for network-control protocols; not forwarded off the local segment |
225.x–238.x | Avoid | Reserved or special-purpose |
References: IANA IPv4 Multicast registry, RFC 2365.
Set up and confirm communication
The goal here is a single confirmed result: one device transmits, another receives, and the received-message counter increases. Complete this before configuring anything else.
1. Create an instance on both devices, using the same address.
- Web interface: open the Local Network Messaging section, add an instance, enter the multicast address (
239.255.0.1:3333), and save. - RPC:
- LNM.Create HTTP GET Request
- LNM.Create Curl Request
- LNM.Create Mos Request
http://192.168.33.1/rpc/LNM.Create?config={"addr":"239.255.0.1:3333"}
curl -X POST -d '{"id":1,"method":"LNM.Create","params":{"config":{"addr":"239.255.0.1:3333"}}}' http://${SHELLY}/rpc
mos --port ${PORT} call LNM.Create '{"config":{"addr":"239.255.0.1:3333"}}'
Response
- LNM.Create HTTP GET Response
- LNM.Create Curl Response
- LNM.Create Mos Response
{
"id": 200
}
{
"id": 1,
"src": "shellyplus1pm-a0b1c2d3e4f5",
"params": {
"id": 200
}
}
{
"id": 200
}
2. On the sending device, enable TX and select a component to send (here, a relay's switch:0).
- Web interface: open the instance, enable TX (Transmit), then choose the component(s) to send.
- RPC:
- LNM.SetConfig HTTP GET Request
- LNM.SetConfig Curl Request
- LNM.SetConfig Mos Request
http://192.168.33.1/rpc/LNM.SetConfig?id=200&config={"tx":{"enable":true,"components":["switch:0"]}}
curl -X POST -d '{"id":1,"method":"LNM.SetConfig","params":{"id":200,"config":{"tx":{"enable":true,"components":["switch:0"]}}}}' http://${SHELLY}/rpc
mos --port ${PORT} call LNM.SetConfig '{"id":200,"config":{"tx":{"enable":true,"components":["switch:0"]}}}'
3. On the receiving device, enable RX.
- Web interface: open the instance and enable RX (Receive).
- RPC:
- LNM.SetConfig HTTP GET Request
- LNM.SetConfig Curl Request
- LNM.SetConfig Mos Request
http://192.168.33.1/rpc/LNM.SetConfig?id=200&config={"rx":{"enable":true}}
curl -X POST -d '{"id":1,"method":"LNM.SetConfig","params":{"id":200,"config":{"rx":{"enable":true}}}}' http://${SHELLY}/rpc
mos --port ${PORT} call LNM.SetConfig '{"id":200,"config":{"rx":{"enable":true}}}'
4. Confirm delivery.
- Web interface: the receiving device's instance shows an RX message counter that increases.
- RPC: read the statistics twice, a few seconds apart:
- LNM.GetStatus HTTP GET Request
- LNM.GetStatus Curl Request
- LNM.GetStatus Mos Request
http://192.168.33.1/rpc/LNM.GetStatus?id=200
curl -X POST -d '{"id":1,"method":"LNM.GetStatus","params":{"id":200}}' http://${SHELLY}/rpc
mos --port ${PORT} call LNM.GetStatus '{"id":200}'
Response
- LNM.GetStatus HTTP GET Response
- LNM.GetStatus Curl Response
- LNM.GetStatus Mos Response
{
"id": 200,
"stats": {
"tx_msgs": 0,
"rx_msgs": 42,
"since": 1774350141
}
}
{
"id": 1,
"src": "shellyplus1pm-a0b1c2d3e4f5",
"params": {
"id": 200,
"stats": {
"tx_msgs": 0,
"rx_msgs": 42,
"since": 1774350141
}
}
}
{
"id": 200,
"stats": {
"tx_msgs": 0,
"rx_msgs": 42,
"since": 1774350141
}
}
If rx_msgs increases, the two devices can hear each other and the group is working. If it stays at 0, see Troubleshooting.
- All devices in a group must be on the same network segment (same Wi-Fi/LAN). Guest and IoT networks are usually isolated from each other.
- A device does not receive its own messages — testing always requires at least two devices.
- Changing the address later requires a device restart (
LNM.SetConfigreturnsrestart_required: true). All other settings apply immediately.
TX, RX, and RPC
Each instance has three independent settings, shown as Enable TX, Enable RX, and Enable RPC in the web interface. Enable any combination per device.
TX (Transmit)
Sends the status of the components you select to the group. Enable it on a device whose state other devices need to observe or react to.
- Example: a Shelly Pro 3EM sends grid power to the group each measurement cycle; an EV charger on the same group reads it and reduces its charging rate when total load is high.
- After enabling TX, select the components to send. Include only what you need — sending fewer components keeps network load low.
Components that can be sent:
| Component | Sent as | Sent when |
|---|---|---|
| Switch | status | Output on/off change (plus periodic updates on metered models) |
| Light / RGB / RGBW / RGBCCT / CCT | status | On/off, brightness, or colour change |
| Cover | status | Open / close / stop / position change |
| Input (switch mode) | status | State change |
| Input (button mode) | event | Button action: single_push, double_push, triple_push, long_push |
| PM1 / EM / EM1 | status | Each measurement cycle |
RX (Receive)
Joins the group and processes incoming messages. Required for a device's Actions (webhooks) or scripts to react to anything on the group.
- Example: a light enables RX so it can act on a button device's presses.
RPC
Enables the LNM.Call mechanism, described in the next section. A device needs RPC enabled to send an LNM.Call and to execute one it receives.
- Example: a Shelly i4 switches every light in the group off with one press.
Which to enable:
| Goal | Sender | Receiver |
|---|---|---|
| Mirror one device's state onto another | TX | RX |
| One device commands a group | RPC | RPC |
| Trigger an Action (webhook) from a received message | TX | RX |
| React in a script to a received message | TX | RX |
Sending commands to a group with LNM.Call
LNM.Call sends a standard RPC method to the whole group. You provide the name of any Shelly RPC method — Switch.Set, RGBCCT.Set, Cover.Open, and so on — together with the parameters that method takes. Every device on the group that has RPC enabled runs the method on itself, exactly as if the call had been made directly on each device. Anything you can do to a single device over its local API can therefore be done to a whole group at once.
1. Enable RPC on the sender and on each receiver (Enable RPC in the web interface, or):
- LNM.SetConfig HTTP GET Request
- LNM.SetConfig Curl Request
- LNM.SetConfig Mos Request
http://192.168.33.1/rpc/LNM.SetConfig?id=200&config={"rpc_enable":true}
curl -X POST -d '{"id":1,"method":"LNM.SetConfig","params":{"id":200,"config":{"rpc_enable":true}}}' http://${SHELLY}/rpc
mos --port ${PORT} call LNM.SetConfig '{"id":200,"config":{"rpc_enable":true}}'
2. Send a command. The parameters are id (the LNM instance), method, and params:
- LNM.Call HTTP GET Request
- LNM.Call Curl Request
- LNM.Call Mos Request
http://192.168.33.1/rpc/LNM.Call?id=200&method="Switch.Set"¶ms={"id":0,"on":true}
curl -X POST -d '{"id":1,"method":"LNM.Call","params":{"id":200,"method":"Switch.Set","params":{"id":0,"on":true}}}' http://${SHELLY}/rpc
mos --port ${PORT} call LNM.Call '{"id":200,"method":"Switch.Set","params":{"id":0,"on":true}}'
Response
- LNM.Call HTTP GET Response
- LNM.Call Curl Response
- LNM.Call Mos Response
{
"id": 200
}
{
"id": 1,
"src": "shellyi4g3-3030f9ecc8f8",
"params": {
"id": 200
}
}
{
"id": 200
}
Delivery is fire-and-forget — receivers' replies are not returned. Further examples:
| Goal | method | params |
|---|---|---|
| Turn all relays off | Switch.Set | {"id":0,"on":false} |
| Set a colour | RGBCCT.Set | {"id":0,"on":true,"rgb":[255,0,0],"brightness":100} |
| Open a cover | Cover.Open | {"id":0} |
RPC must be enabled on both ends: on the sender to be allowed to send (otherwise the call returns error -109, "RPC is not enabled on this instance", and nothing is sent), and on each receiver to execute the command. tx.enable is not required for LNM.Call. Receivers execute the command but never resend it, so there is no risk of a message loop.
For the full LNM.Call definition, see the LNM component reference.
Reacting with Actions (webhooks)
A device with RX enabled can run an Action when it receives a message, configured entirely in the web interface — no scripting needed. When you create a new Action on the receiving device, select the LNM instance (for example LNM (200)) as its component; the two LNM events below then become available as triggers. Each Action has a trigger event, an optional condition that filters it, and one or more actions to run.
| LNM event | Fires when | Available data |
|---|---|---|
LNM status received (lnm.rx_status) | a status message is received | ev.device, ev.status |
LNM event received (lnm.rx_event) | an event (e.g. a button press) is received — one per event | ev.device, ev.event_data |
Filtering with a condition
Add a condition to run the Action only in specific cases. A condition is a short expression:
| Run only when… | Condition |
|---|---|
| …a specific device sent the message | ev.device === "shellyi4g3-3030f9ecc8f8" |
| …a received relay is on | ev.status["switch:0"].output === true |
| …a long button press was received | ev.event_data.event === "long_push" |
The main button gestures to filter on are single_push, double_push, triple_push, and long_push. The stream can also include the lower-level btn_down and btn_up events (the physical press and release).
Choosing what the Action does
There are two ways to define what the Action does:
- A guided action — for LNM, choose LNM Set Output or LNM Toggle Output (pick the component type — Switch, Light, CCT, RGB, RGBW, RGBCCT — the Component ID, and On/Off). This builds an
LNM.Callfor you and is the simplest way to start. - A custom URL — call any endpoint directly. The editor offers quick-insert buttons for the values available on the event:
${ev.device}and${ev.event_data}for LNM event received, or${ev.device}and${ev.status}for LNM status received. Extend these with the specific field you need, for example${ev.status["switch:0"].output}.
The custom-URL examples below use localhost — the device's own loopback address (127.0.0.1), i.e. the device calling itself — so you can test them without any other equipment.
Mirror a received relay state onto this device's relay
- Trigger: LNM status received
http://localhost/rpc/Switch.Set?id=0&on=${ev.status["switch:0"].output}
Toggle this device's relay on a long press
- Trigger: LNM event received
- Condition:
ev.event_data.event === "long_push"
http://localhost/rpc/Switch.Toggle?id=0
Send a group command in response (via LNM.Call)
http://localhost/rpc/LNM.Call?id=200&method=Switch.Set¶ms={"id":0,"on":false}
Actions fire only while RX is enabled, and deleting an LNM instance removes the Actions attached to it. The guided LNM actions currently cover common cases; the web interface for LNM actions will be expanded in future firmware, and the custom-URL option is available for anything not yet covered.
See the Webhook reference for the full Action/webhook API, and the LNM component reference for the event attributes.
Using LNM in scripts
On a device with RX enabled, a script receives an rx event for each incoming message:
{
"name": "lnm",
"info": {
"event": "rx",
"device": "shellyi4g3-3030f9ecc8f8",
"status": { "switch:0": { "output": true } },
"events": [ { "component": "input:0", "id": 0, "event": "single_push" } ]
}
}
ev.info.device— the sender's device IDev.info.status— received component status (present for status messages)ev.info.events— received events such as button presses (present for event messages)
The rx event can fire several times per second, and the stream includes lower-level events (btn_down, btn_up). Filter for the exact event you need, and keep handlers short — avoid issuing many calls per event.
The four examples below are independent — each is a complete script for a single device. For the script event details, see the LNM component reference and the Scripts documentation.
Button remote — runs on the sending device. A Shelly i4 whose four buttons control a Shelly Multicolor Bulb Gen3 on the group. Requires RPC enabled on this instance.
let LNM_ID = 200; // this device's LNM instance id
let TARGET = 0; // rgbcct id on the receiving bulb (a Shelly Multicolor Bulb Gen3)
function lnm(method, params) {
Shelly.call("LNM.Call", { id: LNM_ID, method: method, params: params },
function (res, ec, em) { if (ec !== 0) print("LNM.Call error:", em); });
}
function onButton(inputId, push) {
if (push !== "single_push" && push !== "long_push") return; // ignore lower-level events
if (inputId === 0) lnm("RGBCCT.Set", { id: TARGET, on: true, brightness: 100 });
else if (inputId === 1) lnm("RGBCCT.Set", { id: TARGET, on: false });
else if (inputId === 2) lnm("RGBCCT.Set", { id: TARGET, on: true, brightness: 100, rgb: [255, 0, 0] });
else if (inputId === 3) lnm("RGBCCT.Toggle", { id: TARGET });
}
Shelly.addEventHandler(function (ev) {
if (!ev.component || ev.component.indexOf("input:") !== 0) return;
if (!ev.info || !ev.info.event) return;
onButton(Number(ev.component.split(":")[1]), ev.info.event);
});
Good-night routine — runs on the sending device. Sends a group command to switch every relay and light off. Trigger it however you like — from a Schedule at a fixed time, a button, or (shown here) a timer.
let LNM_ID = 200; // this device's LNM instance id
function goodNight() {
Shelly.call("LNM.Call", { id: LNM_ID, method: "Switch.Set", params: { id: 0, on: false } });
Shelly.call("LNM.Call", { id: LNM_ID, method: "RGBCCT.Set", params: { id: 0, on: false } });
}
// Example trigger: run once every 24 hours. In practice, call goodNight() from a
// Schedule at a fixed time (e.g. 23:00), or from a physical button on this device.
Timer.set(24 * 60 * 60 * 1000, true, goodNight);
React to a received event (a button press) — runs on the receiving device. Turns button presses received from a sender into local light actions. Requires RX enabled here, and the sending device transmitting its inputs.
let TARGET = 0; // local rgbcct id
Shelly.addEventHandler(function (ev) {
if (ev.name !== "lnm" || !ev.info || ev.info.event !== "rx") return;
if (!ev.info.events) return;
let evs = ev.info.events;
for (let i = 0; i < evs.length; i++) {
let e = evs[i];
if (!e.component || e.component.indexOf("input:") !== 0) continue;
if (e.event !== "single_push" && e.event !== "long_push") continue;
let id = Number(e.component.split(":")[1]);
if (id === 0) Shelly.call("RGBCCT.Set", { id: TARGET, on: true, brightness: 100 });
else if (id === 1) Shelly.call("RGBCCT.Set", { id: TARGET, on: false });
else if (id === 2) Shelly.call("RGBCCT.Set", { id: TARGET, on: true, brightness: 100, rgb: [0, 0, 255] });
else if (id === 3) Shelly.call("RGBCCT.Toggle", { id: TARGET });
}
});
React to a received status (mirror a device's state) — runs on the receiving device. Keeps this device's output in sync with a sender's status. Requires RX enabled.
let SENDER_ID = ""; // "" = mirror any sender; or set one device id to lock to it
let SOURCE_KEY = "rgbcct:0"; // component to mirror (use "switch:0" for a relay)
let TARGET = 0; // local rgbcct id
Shelly.addEventHandler(function (ev) {
if (ev.name !== "lnm" || !ev.info || ev.info.event !== "rx") return;
if (SENDER_ID && ev.info.device !== SENDER_ID) return;
let st = ev.info.status;
if (!st || !st[SOURCE_KEY]) return;
if (typeof st[SOURCE_KEY].output === "boolean") {
Shelly.call("RGBCCT.Set", { id: TARGET, on: st[SOURCE_KEY].output });
}
});
Troubleshooting
If rx_msgs does not increase, the cause is almost always the network, not LNM:
| Check | Action |
|---|---|
| Same address and port | Confirm with LNM.GetConfig?id=200 on each device. |
| Recommended range | Use 239.255.x.y; move off 224.0.0.x and other reserved ranges. |
| Same network segment | All devices on one Wi-Fi/LAN; guest and IoT networks are isolated. |
| AP / Client Isolation | This router setting blocks device-to-device traffic; disable it for the devices' network. |
| IGMP snooping / querier | Networks use IGMP to manage which ports receive multicast. With IGMP snooping enabled but no active querier, a group's membership can age out and delivery may stop after a while. If the router exposes these settings, pair snooping with an IGMP querier (or proxy); if it still drops traffic, disabling snooping can help. A router restart restores delivery temporarily. |
| Mesh Wi-Fi | Many mesh systems expose no multicast setting; keep sender and receiver on the same network name and use a 239.255.x.y address. |
Because delivery is fire-and-forget, occasional message loss over UDP multicast is normal and is not a fault.
Reference
- LNM component reference — all RPC methods, the configuration object, and the binary wire format
- Actions / Webhooks — creating event-triggered HTTP actions and conditions
- Scripts — writing on-device automation in JavaScript