@bobfrankston/wzlan
v0.1.10
Published
Wiz LAN protocol library for device control via UDP
Maintainers
Readme
@bobfrankston/wzlan
Platform-agnostic Wiz LAN protocol library for controlling Wiz smart bulbs via UDP.
Architecture
wzlan (protocol core, platform-agnostic)
├── wzlan-node (Node.js adapter — dual-socket UDP via rmfudp)
└── wzlan-browser (Browser adapter — WebSocket via httpudp-client)Wiz uses JSON-over-UDP on two ports:
- 38899 — Send commands, receive responses
- 38900 — Receive heartbeat/syncPilot status (after registration)
Quick Start (Node.js)
import { createClient } from '@bobfrankston/wzlan-node';
const client = createClient();
client.on('device', (device) => {
console.log(`Found: ${device.mac} ${device.ip}`);
});
client.on('state', (device) => {
console.log(`${device.mac} power=${device.power} dim=${device.dimming} temp=${device.temp}`);
});
await client.start(); // Binds sockets, starts registration loop
// Heartbeats arrive automatically (~1/sec per bulb for ~20s, re-registered every 15s)API
WzClient
Created via createClient() (wzlan-node) or createWzlanClient() (wzlan-browser).
| Method | Description |
|--------|-------------|
| start() | Bind transport, start registration loop |
| stop() | Close sockets, stop timers |
| register(heartbeats?) | Broadcast registration (true=heartbeats, false=discovery only) |
| discover() | Shorthand for register(false) |
| getDevice(mac) | Look up device by MAC |
| addDevice(mac, ip?, port?) | Add device manually |
| devices | Map<string, WzDevice> of all known devices |
Events
| Event | Args | Description |
|-------|------|-------------|
| device | (device) | New device discovered |
| state | (device) | State updated (syncPilot heartbeat or setPilot response) |
| pilot | (device, msg) | getPilot response received |
| systemConfig | (device, msg) | getSystemConfig response received |
| message | (device, msg) | Any message received |
| error | (err) | Transport or protocol error |
WzDevice
Extends DeviceBase from @bobfrankston/devdefs — shared state (mac, ip, port, power, label, online, lastSeen, markSeen()) and transport plumbing. See devdefs README for base class details and MAC utilities (mac12, macmac).
Properties updated automatically from heartbeats:
| Property | Type | Description |
|----------|------|-------------|
| mac | string | MAC address (lowercase, colon-separated) |
| ip | string | Current IP address |
| power | boolean | On/off state |
| dimming | number | Brightness 0-100 |
| color | WizColor | { r, g, b, c, w } channels (0-255) |
| temp | number | Color temperature in Kelvin |
| sceneId | number | Active scene (0 = none) |
| speed | number | Animation speed |
| rssi | number | WiFi signal strength (dBm) |
| fwVersion | string | Firmware version |
| online | boolean | Responding to messages |
| lastSeen | number | Timestamp of last message |
Control Methods (fire-and-forget)
device.setPower(true); // Turn on
device.setPower(false); // Turn off
device.setBrightness(75); // Set brightness 0-100
device.setColor(255, 0, 0); // Set RGB
device.setColor(0, 255, 0, 50); // Set RGB with dimming
device.setWhite(4200); // Set color temperature
device.setWhite(2700, 80); // Set temp with dimming
device.setScene(4); // Set scene by ID
device.setScene(5, 100, 80); // Scene with speed and dimming
device.pulse(-30, 900); // Pulse brightness
device.getPilot(); // Query current state
device.getSystemConfig(); // Query system configProtocol Functions
For building custom messages:
import { encodeMessage, decodeMessage, createSetPilot, createRegistration } from '@bobfrankston/wzlan';
const msg = createSetPilot({ state: true, dimming: 75 });
const bytes = encodeMessage(msg);
// Send bytes via UDP to port 38899
const response = decodeMessage(receivedBytes);Scenes
| ID | Name | ID | Name | |----|------|----|------| | 1 | Ocean | 17 | True Colors | | 2 | Romance | 18 | TV Time | | 3 | Sunset | 19 | Plant Growth | | 4 | Party | 20 | Spring | | 5 | Fireplace | 21 | Summer | | 6 | Cozy | 22 | Fall | | 7 | Forest | 23 | Deep Dive | | 8 | Pastel Colors | 24 | Jungle | | 9 | Wake Up | 25 | Mojito | | 10 | Bedtime | 26 | Club | | 11 | Warm White | 27 | Christmas | | 12 | Daylight | 28 | Halloween | | 13 | Cool White | 29 | Candlelight | | 14 | Night Light | 30 | Golden White | | 15 | Focus | 31 | Pulse | | 16 | Relax | 32 | Steampunk | | 1000 | Rhythm | | |
Constants
import { WIZ_PORT, WIZ_LISTEN_PORT, WizMethod, WizScenes } from '@bobfrankston/wzlan';
WIZ_PORT // 38899 — command port
WIZ_LISTEN_PORT // 38900 — heartbeat port
WizMethod.SetPilot // 'setPilot'
WizMethod.GetPilot // 'getPilot'
WizMethod.SyncPilot // 'syncPilot'
WizMethod.Registration // 'registration'
WizScenes[12] // 'Daylight'wzlan-node
Dual-socket transport: ephemeral port for commands (→38899), port 38900 for heartbeats. Both merged into single message handler.
import { createClient } from '@bobfrankston/wzlan-node';
const client = createClient({
debug: true, // Log messages to console
registrationInterval: 15000, // Re-register every 15s (default)
homeId: 704603, // Optional home ID
});wzlan-browser
WebSocket transport via httpudp proxy. The httpudp server handles dual-port proxying server-side.
import { createWzlanClient } from '@bobfrankston/wzlan-browser';
const client = await createWzlanClient({
httpudpUrl: 'ws://localhost:9321',
registrationInterval: 15000,
});wztest CLI
Simple test/control tool:
wztest Interactive mode
wztest list List discovered bulbs
wztest <mac> -on [dim] Turn on
wztest <mac> -off Turn off
wztest <mac> -dim 75 Set dimming
wztest <mac> -color 255 0 0 Set RGB
wztest <mac> -white 4200 Set white temp
wztest <mac> -scene 4 Set scene (Party)
wztest -v Verbose (show heartbeats)Device can be partial MAC, partial IP, or #N from list.
