zwavejs

package module
v0.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: MIT Imports: 9 Imported by: 0

README

zwavejs-go

A standalone Go client for zwave-js-server's WebSocket JSON protocol — the Node.js bridge most self-hosted Z-Wave setups already run. No dependency on any particular application; it speaks the protocol and nothing else, the same separation pyatv draws between "speak the device's protocol" and "whatever app is using it."

Talks to zwave-js-server directly, not through Home Assistant — useful when you want raw Z-Wave control (config parameter writes, value reads, live value-updated events) without going through HA's own zwave_js integration at all. See inovelli for a consumer built on top of this for Inovelli switch control.

Install

go get github.com/ryanjohnsontv/zwavejs-go

Quick start

client, err := zwavejs.Dial(ctx, "192.168.1.10:3000")
if err != nil {
    log.Fatal(err)
}
defer client.Close()

nodes, err := client.StartListening(ctx) // the initial dump of every known node

Dial completes the version handshake and starts the background read loop, but doesn't call StartListening itself — that's a separate step since it's the one that triggers the (potentially large, on a real network) initial node/value dump, and callers should be ready for it before asking for it.

Reading and writing values

if err := client.SetValue(ctx, nodeID, zwavejs.ValueID{
    CommandClass: 112, // Configuration
    Property:     13,
}, 85); err != nil {
    log.Fatal(err)
}

ValueID.Property/PropertyKey are any because zwave-js-server's own protocol uses either a string or a number for them depending on the command class (e.g. a Configuration CC parameter index is numeric; Central Scene's property is the literal string "scene").

MulticastSetValue(ctx, nodeIDs, valueID, value) sets the same value across several nodes in one real Z-Wave multicast frame instead of one sequential unicast per node — every targeted node must support the same value (e.g. every node in a "living room switches" group having the same Configuration CC parameter).

Live events

for evt := range client.Events() {
    fmt.Println(evt.NodeID, evt.ValueID, evt.NewValue)
}

Only value updated events are modeled — node added/removed and controller/driver events are ignored rather than modeled speculatively, since nothing currently built on this client needs them. Events() returns a channel of capacity 64; if a consumer falls behind, new events are dropped (logged, not blocked) rather than backing up the read loop.

Reconnection

This package doesn't reconnect on its own — Dial returns an error if the connection drops, and it's the caller's responsibility to redial and re-run StartListening if it needs the reconstructed state. This is a deliberate difference from haws, which does own its reconnection lifecycle: a zwave-js-server session's value cache is meaningful state to rebuild deliberately, not something to paper over with an automatic retry loop.

WebSocket transport

Uses wsconn — a minimal, zero-dependency RFC 6455 client.

Feedback

Issues, questions, and PRs are welcome — this is early, and real-world feedback (what broke, what's confusing, what's missing) is genuinely useful.

Documentation

Overview

Package zwavejs is a standalone client for zwave-js-server's WebSocket JSON protocol (github.com/zwave-js/zwave-js-server) — the Node.js bridge most self-hosted Z-Wave setups already run. It has no dependency on any particular application; it's meant to be usable on its own, the same separation pyatv draws between "speak the device's protocol" and "whatever app is using it."

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a connected zwave-js-server session.

func Dial

func Dial(ctx context.Context, addr string) (*Client, error)

Dial connects to a zwave-js-server instance at addr ("host:port"), completes the version handshake, and starts the background read loop. It does not yet call StartListening — the caller does that once it's ready to receive the initial node dump and live events.

func (*Client) Close

func (c *Client) Close() error

Close closes the underlying connection.

func (*Client) Events

func (c *Client) Events() <-chan ValueUpdatedEvent

Events returns the channel of "value updated" events. Other event types (node added/removed, controller/driver events, ...) are not modeled and are silently dropped.

func (*Client) MulticastSetValue

func (c *Client) MulticastSetValue(ctx context.Context, nodeIDs []int, valueID ValueID, value any) error

MulticastSetValue sets the same value on the same ValueID across multiple nodes in a single command (multicast_group.set_value) — one real Z-Wave multicast frame instead of len(nodeIDs) sequential unicasts. All targeted nodes must support the same value (e.g. every node in a "living room switches" group having a Binary Switch currentValue); a node that doesn't have a matching ValueID will just fail to apply it.

func (*Client) SetValue

func (c *Client) SetValue(ctx context.Context, nodeID int, valueID ValueID, value any) error

SetValue sends node.set_value to change one value on one node.

func (*Client) StartListening

func (c *Client) StartListening(ctx context.Context) ([]Node, error)

StartListening sends the start_listening command and returns the server's initial dump of every known node.

type Node

type Node struct {
	NodeID int    `json:"nodeId"`
	Status int    `json:"status"`
	Ready  bool   `json:"ready"`
	Name   string `json:"name,omitempty"`
	Label  string `json:"label,omitempty"`
	// ManufacturerID/ProductID/ProductType identify the device model —
	// same triple Home Assistant's zwave_js integration keys its own
	// per-device quirks on, for devices whose reported generic/specific
	// Z-Wave device class doesn't disambiguate what a value actually
	// controls (e.g. the Inovelli LZW36's fan endpoint, which reports
	// generic "Multilevel Switch" / specific "Not Used" instead of the
	// standard "Fan Switch" specific device class).
	ManufacturerID int     `json:"manufacturerId"`
	ProductID      int     `json:"productId"`
	ProductType    int     `json:"productType"`
	Values         []Value `json:"values"`
}

Node is one Z-Wave device as reported by start_listening's initial state dump. This only carries the fields this package actually uses — zwave-js-server's real node object has many more.

type Value

type Value struct {
	ValueID
	PropertyName    string        `json:"propertyName,omitempty"`
	PropertyKeyName string        `json:"propertyKeyName,omitempty"`
	Metadata        ValueMetadata `json:"metadata"`
	Value           any           `json:"value"`
}

Value is one entry in a Node's Values — a ValueID plus its current value and metadata, as returned in the initial state dump.

type ValueID

type ValueID struct {
	CommandClass int `json:"commandClass"`
	Endpoint     int `json:"endpoint,omitempty"`
	Property     any `json:"property"`
	PropertyKey  any `json:"propertyKey,omitempty"`
}

ValueID identifies a single value on a node — commandClass + property (+ endpoint/propertyKey for multi-channel or sub-indexed values). Property and PropertyKey are `any` because zwave-js-server's protocol uses either a string or a number for them depending on the command class (e.g. "targetValue" vs. a numeric configuration parameter index).

type ValueMetadata

type ValueMetadata struct {
	Type      string `json:"type"` // "boolean", "number", "string", ...
	Readable  bool   `json:"readable"`
	Writeable bool   `json:"writeable"`
	Label     string `json:"label"`
	Unit      string `json:"unit,omitempty"`
}

ValueMetadata describes a value's type and capabilities, as reported in a node's initial value dump.

type ValueUpdatedEvent

type ValueUpdatedEvent struct {
	NodeID    int
	ValueID   ValueID
	PrevValue any
	NewValue  any
}

ValueUpdatedEvent is a "value updated" event, the one this package actually acts on to keep entity state in sync — other event types (node added/removed, controller/driver events, ...) are ignored for now rather than modeled speculatively.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL