lifxlan v0.0.67
No dependencies. Bring your own socket.
Works with Node.js, Bun, and Deno.
Examples
Node.js / Bun (udp broadcast is not supported in Bun yet https://github.com/oven-sh/bun/issues/10381)
import dgram from 'node:dgram';
import { Client, Router, Devices, GetServiceCommand } from 'lifxlan';
const socket = dgram.createSocket('udp4');
// Router handles outgoing messages and forwards responses to clients
const router = Router({
onSend(message, port, address) {
// A message is ready to be sent
socket.send(message, port, address);
},
});
// Devices keeps track of devices discovered on the network
const devices = Devices({
onAdded(device) {
// A device has been discovered
console.log(device);
},
});
socket.on('message', (message, remote) => {
// Forward received messages to the router
const { header, serialNumber } = router.receive(message);
// Forward the message to devices so it can keep track
devices.register(serialNumber, remote.port, remote.address, header.target);
});
// Client handles communication with devices
const client = Client({ router });
socket.once('listening', () => {
// Broadcast is not supported in Bun yet https://github.com/oven-sh/bun/issues/10381
socket.setBroadcast(true);
// Discover devices on the network
client.broadcast(GetServiceCommand());
});
socket.bind();
setTimeout(() => {
socket.close();
}, 1000);
Deno
import { Client, Router, Devices, GetServiceCommand } from 'lifxlan';
const socket = Deno.listenDatagram({
hostname: '0.0.0.0',
port: 0,
transport: 'udp',
});
const router = Router({
onSend(message, port, hostname) {
socket.send(message, { port, hostname });
}
});
const devices = Devices({
onAdded(device) {
console.log(device);
},
});
const client = Client({ router });
client.broadcast(GetServiceCommand());
setTimeout(() => {
socket.close();
}, 1000);
for await (const [message, remote] of socket) {
const { header, serialNumber } = router.receive(message);
devices.register(serialNumber, remote.port, remote.hostname, header.target);
}
How to turn a light on
import dgram from 'node:dgram';
import { Client, Devices, Router, GetServiceCommand, SetPowerCommand } from 'lifxlan';
const socket = dgram.createSocket('udp4');
const router = Router({
onSend(message, port, address) {
socket.send(message, port, address);
},
});
const devices = Devices();
socket.on('message', (message, remote) => {
const { header, serialNumber } = router.receive(message);
devices.register(serialNumber, remote.port, remote.hostname, header.target);
});
await new Promise((resolve, reject) => {
socket.once('error', reject);
socket.once('listening', resolve);
socket.bind();
});
socket.setBroadcast(true);
const client = Client({ router });
// Start scanning for devices
client.broadcast(GetServiceCommand());
const scanInterval = setInterval(() => {
client.broadcast(GetServiceCommand());
}, 1000);
const device = await devices.get('d07123456789');
// Stop scanning since device was found
clearInterval(scanInterval);
await client.sendOnlyAcknowledge(SetPowerCommand(true), device);
socket.close();
How to retry
for (let i = 0; i < 3; i++) {
try {
console.log(await client.send(GetColorCommand(), device));
break;
} catch (err) {
const delay = Math.random() * Math.min(Math.pow(2, i) * 1000, 30 * 1000);
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
How to specify a custom timeout
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort();
}, 100);
try {
console.log(await client.send(GetColorCommand(), device, controller.signal));
} finally {
clearTimeout(timeout)
}
How to use without device discovery
import dgram from 'node:dgram';
import { Client, Device, Router, GetServiceCommand, SetPowerCommand } from 'lifxlan';
const socket = dgram.createSocket('udp4');
const router = Router({
onSend(message, port, address) {
socket.send(message, port, address);
},
});
socket.on('message', (message, remote) => {
router.receive(message);
});
await new Promise((resolve, reject) => {
socket.once('error', reject);
socket.once('listening', resolve);
socket.bind();
});
const client = Client({ router });
// Create the device directly
const device = Device({
serialNumber: 'd07123456789',
address: '192.168.1.50',
});
await client.sendOnlyAcknowledge(SetPowerCommand(true), device);
socket.close();
How to create a custom command
/**
* @param {Uint8Array} bytes
* @param {{ current: number; }} offsetRef
*/
function decodeCustom(bytes, offsetRef) {
const val1 = bytes[offsetRef.current++];
const val2 = bytes[offsetRef.current++];
return {
val1,
val2,
};
}
function CustomCommand() {
return {
type: 1234,
decode: decodeCustom,
};
}
const res = await client.send(CustomCommand(), device);
console.log(res.val1, res.val2);
How to use multiple clients
import dgram from 'node:dgram';
import { Client, Devices, Router, GetServiceCommand, SetPowerCommand } from 'lifxlan';
const socket = dgram.createSocket('udp4');
const router = Router({
onSend(message, port, address) {
socket.send(message, port, address);
},
});
const devices = Devices();
socket.on('message', (message, remote) => {
const { header, serialNumber } = router.receive(message);
devices.register(serialNumber, remote.port, remote.hostname, header.target);
});
await new Promise((resolve, reject) => {
socket.once('error', reject);
socket.once('listening', resolve);
socket.bind();
});
socket.setBroadcast(true);
const client1 = Client({ router });
const client2 = Client({ router });
client1.broadcast(GetServiceCommand());
const scanInterval = setInterval(() => {
client1.broadcast(GetServiceCommand());
}, 1000);
const device = await devices.get('d07123456789');
clearInterval(scanInterval);
await client2.sendOnlyAcknowledge(SetPowerCommand(true), device);
socket.close();
How to use a lot of clients
while (true) {
const client = Client({ router });
console.log(await client.send(GetPowerCommand(), device));
// When creating a lot of clients, call dispose to avoid running out of source values
client.dispose();
}
How to use one socket for broadcast messages and another socket for unicast messages
import dgram from 'node:dgram';
import { Client, Devices, GetServiceCommand } from 'lifxlan';
const broadcastSocket = dgram.createSocket('udp4');
const unicastSocket = dgram.createSocket('udp4');
const router = Router({
onSend(message, port, address, serialNumber) {
if (!serialNumber) {
broadcastSocket.send(message, port, address);
} else {
unicastSocket.send(message, port, address);
}
},
});
const devices = Devices();
/**
* @param {Uint8Array} message
* @param {{ port: number; address: string; }} remote
*/
function onMessage(message, remote) {
const { header, serialNumber } = router.receive(message);
devices.register(serialNumber, remote.port, remote.address, header.target);
}
broadcastSocket.on('message', onMessage);
unicastSocket.on('message', onMessage);
await Promise.all(
[broadcastSocket, unicastSocket].map((socket) => (
new Promise((resolve, reject) => {
socket.once('error', reject);
socket.once('listening', resolve);
socket.bind();
})),
),
);
broadcastSocket.setBroadcast(true);
const client = Client({ router });
client.broadcast(GetServiceCommand());
const scanInterval = setInterval(() => {
client.broadcast(GetServiceCommand());
}, 1000);
const device = await devices.get('d07123456789');
clearInterval(scanInterval);
await client.sendOnlyAcknowledge(SetPowerCommand(true), device);
broadcastSocket.close();
unicastSocket.close();
How to use one socket per device
import dgram from 'node:dgram';
import { Client, Device, Router, Devices, GetServiceCommand, SetColorCommand } from 'lifxlan';
/**
* @param {Uint8Array} message
* @param {{ port: number; address: string; }} remote
*/
function onMessage(message, remote) {
const { header, serialNumber } = router.receive(message);
devices.register(serialNumber, remote.port, remote.address, header.target);
}
const broadcastSocket = dgram.createSocket('udp4');
broadcastSocket.on('message', onMessage);
await new Promise((resolve, reject) => {
broadcastSocket.once('error', reject);
broadcastSocket.once('listening', resolve);
broadcastSocket.bind();
});
broadcastSocket.setBroadcast(true);
/**
* @type {Map<string, dgram.Socket>}
*/
const deviceSockets = new Map();
const router = Router({
onSend(message, port, address, serialNumber) {
if (!serialNumber) {
broadcastSocket.send(message, port, address);
} else {
const socket = deviceSockets.get(serialNumber);
if (socket) {
socket.send(message);
}
}
},
});
/**
* @param {Device} device
*/
function setupDeviceSocket(device) {
const socket = dgram.createSocket('udp4');
socket.on('message', onMessage);
socket.bind();
socket.connect(device.port, device.address);
deviceSockets.set(device.serialNumber, socket);
}
const devices = Devices({
onAdded(device) {
setupDeviceSocket(device);
},
onChanged(device) {
const oldSocket = deviceSockets.get(device.serialNumber);
if (oldSocket) {
deviceSockets.delete(device.serialNumber);
oldSocket.close();
}
setupDeviceSocket(device);
},
});
const client = Client({ router });
client.broadcast(GetServiceCommand());
setInterval(() => {
client.broadcast(GetServiceCommand());
}, 5000);
const PARTY_COLORS = /** @type {const} */ ([
[48241, 65535, 65535, 3500],
[43690, 49151, 65535, 3500],
[54612, 65535, 65535, 3500],
[43690, 65535, 65535, 3500],
[38956, 55704, 65535, 3500],
]);
while (true) {
const deviceCount = devices.registered.size;
if (deviceCount > 0) {
const waitTime = Math.min(2000 / deviceCount, 100);
for (const device of devices.registered.values()) {
const [hue, saturation, brightness, kelvin] = PARTY_COLORS[Math.random() * PARTY_COLORS.length | 0];
client.unicast(SetColorCommand(hue, saturation, brightness, kelvin, 2000), device);
await new Promise((resolve) => setTimeout(resolve, waitTime));
}
} else {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
Same as the previous example but with only one socket
import dgram from 'node:dgram';
import { Client, Router, Devices, GetServiceCommand, SetColorCommand } from 'lifxlan';
const socket = dgram.createSocket('udp4');
socket.on('message', (message, remote) => {
const { header, serialNumber } = router.receive(message);
devices.register(serialNumber, remote.port, remote.address, header.target);
});
await new Promise((resolve, reject) => {
socket.once('error', reject);
socket.once('listening', resolve);
socket.bind();
});
socket.setBroadcast(true);
const router = Router({
onSend(message, port, address) {
socket.send(message, port, address);
},
});
const devices = Devices();
const client = Client({ router });
client.broadcast(GetServiceCommand());
setInterval(() => {
client.broadcast(GetServiceCommand());
}, 250);
const PARTY_COLORS = /** @type {const} */ ([
[48241, 65535, 65535, 3500],
[43690, 49151, 65535, 3500],
[54612, 65535, 65535, 3500],
[43690, 65535, 65535, 3500],
[38956, 55704, 65535, 3500],
]);
while (true) {
const deviceCount = devices.registered.size;
if (deviceCount > 0) {
const waitTime = Math.min(2000 / deviceCount, 100);
console.log(deviceCount);
for (const device of devices.registered.values()) {
const [hue, saturation, brightness, kelvin] = PARTY_COLORS[Math.random() * PARTY_COLORS.length | 0];
client.unicast(SetColorCommand(hue, saturation, brightness, kelvin, 2000), device);
await new Promise((resolve) => setTimeout(resolve, waitTime));
}
} else {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
How to discover and organize devices into groups
import dgram from 'node:dgram';
import { Client, Router, Devices, Groups, GetServiceCommand, GetGroupCommand } from 'lifxlan';
const socket = dgram.createSocket('udp4');
await new Promise((resolve, reject) => {
socket.once('error', reject);
socket.once('listening', resolve);
socket.bind();
});
socket.setBroadcast(true);
const router = Router({
onSend(message, port, address) {
socket.send(message, port, address);
},
});
const client = Client({ router });
const groups = Groups({
onAdded(group) {
console.log('Group added', group);
},
onChanged(group) {
console.log('Group changed', group);
},
});
const devices = Devices({
async onAdded(device) {
const group = await client.send(GetGroupCommand(), device);
groups.register(device, group);
},
});
socket.on('message', (message, remote) => {
const { header, serialNumber } = router.receive(message);
devices.register(serialNumber, remote.port, remote.address, header.target);
});
client.broadcast(GetServiceCommand());
const scanInterval = setInterval(() => {
client.broadcast(GetServiceCommand());
}, 250);
setTimeout(() => {
clearInterval(scanInterval);
socket.close();
}, 2000);
How to send a command to all devices discovered in a group
await Promise.all(group.devices.map((device) => client.send(GetLabelCommand(), device, signal)));
How to keep group devices sorted when the devices are discovered or removed
const groups = Groups({
onChanged(group) {
group.devices.sort((deviceA, deviceB) => {
if (deviceA.serialNumber < deviceB.serialNumber) {
return -1;
}
return 1;
});
}
});
How to run a callback for every message received by a router
import { Router } from 'lifxlan';
const router = Router({
onMessage(header, payload, serialNumber) {
// Called for every message received by the router
},
});
How to run a callback for every message received by a client
import { Client, Router } from 'lifxlan';
const router = Router({
onSend(message, port, address) {
// Send the message over the socket
},
});
const client = Client({
router,
onMessage(header, payload, serialNumber) {
// Called for every message received by the client
},
});
// ...
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
10 months ago
11 months ago
10 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago