Z-Wave
Driver: zwave
The Z-Wave page is where you pair (include) and remove (exclude) Z-Wave devices, turn the nodes on the mesh into GEM zones, and wire each zone's buttons and feedback to specific Z-Wave values.
Open Z-Wave
The page lives under Integrations > Z-Wave. It talks to a USB Z-Wave stick plugged into the GEM server — Aeotec Z-Stick, Zooz ZST10, Nortek/GoControl HUSBZB-1 and similar. One stick is one controller device in GEM; everything on this page acts on the controller you select at the top.
Including a node does not create a zone, and creating a zone does not make it controllable. A Z-Wave zone works only after you have (1) created it with a node:endpoint address, (2) mapped each button to a Z-Wave value in Command Mappings, and (3) mapped each piece of feedback in Response Mappings. All three steps are on this page, and the sections below follow that order.
Before you start — the controller device
Add the stick as a device
Go to Devices and click Add (when no controller exists yet, the Z-Wave page's empty state offers an Add a controller link that opens the same device editor in a modal and selects the new controller once saved).
| Field | What to enter |
|---|---|
| Name | Lowercase name, e.g. zwave_controller. |
| Driver | zwave. |
| Type | Controller or hub. |
| Address | The serial path of the stick — /dev/ttyUSB0, /dev/ttyACM0, or a /dev/serial/by-id/... path. |
| Command Set | Required. Picking the driver auto-selects (and creates, on first use) a command set named zwave. |
| Status | On. |
| Elevated | Turn on only if everything on this stick is sensitive. Most sites leave it off here and set Elevated on the individual lock or gate zone instead, so lighting on the same stick stays available to ordinary roles. |
Address is the field the driver reads. The Device Attributes section also offers Serial Port (alt) (port), which is copied into Address when the controller connects — useful if a site already stores the path there. The Serial Path attribute in the same list is not used; leave it empty and put the path in Address.
On connect, the driver promotes a /dev/tty* address to its stable /dev/serial/by-id/... symlink so the controller survives replug and reboot reordering, and saves the new path back onto the device. If the configured path no longer exists (the dongle moved to a different USB port), it scans the by-id entries for a known Z-Wave descriptor — Silicon Labs HubZ -if00, Aeotec Z-Stick, generic Z-Stick, UZB, Zooz, Nortek, Sigma Designs — and points itself at the match. On a HubZ combo stick the -if00 interface is the Z-Wave radio and -if01 is Zigbee, which is why the match includes the suffix.
To switch this off, add a device attribute usb_auto_discover set to false. Worth doing only on a host with two identical Z-Wave sticks, where the wrong one could be picked up.
Security keys are minted for you
The first time the controller connects, GEM generates the four Z-Wave network keys (S0 Legacy, S2 Unauthenticated, S2 Authenticated, S2 Access Control) as random 32-character hex strings and stores them together in one encrypted, read-only device attribute named security_keys. You never type them in — the individual key fields listed under Device Attributes are not what the driver reads.
If that attribute is deleted or replaced, every already-included secure device (locks especially) has to be excluded and re-included. Node pairings live with the stick and its own cache on the GEM server, not in GEM's database — replacing or re-flashing the stick means re-including every node.
The zone template — what makes a button work
A Z-Wave zone's buttons reach the stick only when the matching row in the controller's command set carries the template zone. On the controller device, open the Commands tab and check the Template column for the verbs you intend to use:
| Verb | Typical use |
|---|---|
on, off | Switches, dimmers, outlets, fans |
level | Dimmer and shade position (also what the Light Dimmer slider sends) |
high, medium, low | Three-speed fans |
raise, lower | Ramping controls |
lock, unlock | Door locks |
open, close, pulse | Garage doors, gates, relay-driven loads |
get_state, get_level | Present in the command set; see the note below before mapping them |
Rows that GEM filled in from the driver are flagged DRIVER in the Source column and are locked — the editor and the inline grid both refuse changes to a driver-managed name, template or arguments. So the templates need to be right before the rows become driver-managed:
- Before the rows are locked: use Add/import command set (the small + next to the Command Set picker on the device), check the library list for a Z-Wave set, and assign it to the controller — imported rows keep the template they arrive with.
- On a set whose rows are already locked, ask the AI Assistant to set the template on those command rows. It writes command rows directly and can overwrite existing ones, which is the way past the editor lock.
A verb whose Template is empty never reaches the Z-Wave dispatch: the button comes back with unknown command and nothing moves.
Everything in a zone's command map sends a value to the node — including get_state and get_level if you map them. Reading values back is what Response Mappings are for. Leave the get_* verbs unmapped.
Page layout
The controller strip runs across the top: the controller picker plus the enrollment, removal and reset buttons. Below it, the Z-Wave Nodes list sits on the left and Node Configuration fills the right. Until you pick a controller the page shows No Controller Selected; until you pick a node the right panel prompts you to select one.
Controller section
Controller picker
The dropdown lists every device using the zwave driver as [id] name (address). Choosing one loads its nodes, subscribes the page to live controller events, and enables the buttons beside it.
Enrollment, removal, reset
| Group | Button | What it does |
|---|---|---|
| Enrollment | Start | Puts the controller into inclusion mode after a confirmation prompt. |
| Enrollment | Stop | Ends inclusion mode. Disabled until inclusion is running. |
| Removal | Start | Puts the controller into exclusion mode after a confirmation prompt. |
| Removal | Stop | Ends exclusion mode. Disabled until exclusion is running. |
| Reset | Soft-resets the controller after a confirmation warning that active enrollments are interrupted. GEM disconnects from the stick, waits two seconds and reconnects — pairings are kept. |
While inclusion is running, the removal Start button is disabled, and vice versa. Toasts report each state change as it happens.
Enrolling a new device
- Select the controller.
- Click Start under Enrollment and confirm.
- Put the physical device into pairing mode (usually a triple-tap on the paddle or a small button — check the manufacturer's instructions).
- Wait for the pairing to complete. A toast names the node when it is added, and the node list refreshes on its own.
- Click Stop when you are done adding devices.
The Start button runs the controller's default inclusion strategy. When a device asks GEM to grant security classes or to validate a DSK and enter the 5-digit PIN printed on the device, GEM records the request and carries on — it does not prompt you, so an S2 pairing that requires that confirmation will not finish. Devices that pair unauthenticated (most switches, dimmers, sensors) include normally.
If you need a specific strategy, run the start_inclusion command from the controller's Commands tab with a strategy argument of default, smart_start, insecure, security_s0 or security_s2 — see Maintenance commands below.
Removing a device
- Select the controller.
- Click Start under Removal and confirm.
- Trigger the exclusion sequence on the physical device.
- A toast names the node when it is removed and the list refreshes.
- Click Stop to leave removal mode.
Exclusion works on devices that were paired to a different controller, so it doubles as a factory reset before re-enrolling gear pulled from another system.
Nodes panel
The left panel lists every node the controller knows about, as [node id] manufacturer description. The reload icon in the panel header re-reads the list from the stick. Nodes the controller has not yet interviewed show as Unknown with no description — reload after a minute, or run refresh_node_info for that node.
Click a single node to load it. The list allows multi-select, but only one node at a time can be configured; selecting more than one shows a warning and loads nothing.
Selecting a node reads its full value list from the stick, which is what fills the Endpoint and Command Class dropdowns, and lists the zones already created against that node.
Node Configuration panel
Zone Management
Existing Zones lists the zones already created on this controller whose address begins with the selected node id. Picking one loads its subsystem, endpoint and control type, locks those three fields, and reveals the Mappings tabs for that zone.
Open opens the selected zone's full editor — attributes, programming, history — in a modal, so the node selection and any mapping work in progress on this page survive the detour.
Delete removes the selected zone. It asks for confirmation first, then deletes the zone, every attribute on it (including its command and response maps) and every UI zone assignment pointing at it.
To create a new zone for the node:
| Field | What to enter |
|---|---|
| Subsystem | Required. The subsystem the zone belongs to — lights, shades, doors, climate, and so on. |
| Endpoint | Required. Pick from the endpoints this node actually exposes. Single-load devices have only 0; a two-relay module or a dual outlet exposes 1 and 2 as well. |
| Control Type | The zone control the UI should render — light_dimmer, light_switch, door_lock and so on. The list offers every control already in use on the site, and you can type a new one. |
Click Create Zone. GEM asks for the zone name, pre-filled from the node's manufacturer and model, and creates an enabled zone whose address is node:endpoint (for example 12:0) and whose device is this controller.
A Z-Wave zone address must be node:endpoint. An address without the colon is logged as invalid when the controller connects and the zone is never controllable. Zones created on this page always get it right; zones created by hand on the Zones page do not.
Command Class Configuration
This section is where you find the specific Z-Wave value behind a function, and test it before you map it.
Select the Endpoint first. The Command Class dropdown only lists values belonging to the endpoint chosen above it, so while Endpoint is empty the dropdown is empty too. Each entry reads [class number] Class Name property (endpoint n) — for example [38] Multilevel Switch currentValue (endpoint 0).
Choosing a value reads its metadata from the node, and the controls that appear depend on what came back:
Writable values get a Value box and a Test Set button. The box adapts to the value's type — a true/false dropdown for boolean, a list of named states for values with a state table (thermostat modes, lock modes), or a plain typed input for numbers and strings. Test Set writes the value to the node immediately.
Read-only values show Read-only value and a Test Get button, which reads the current value and prints it beside the label.
Test before you map. If Test Set does not move the load, the mapping will not either — that is a Z-Wave problem (wrong value, sleeping device, out of range), not a GEM one.
Mappings
The Command Mappings and Response Mappings tabs appear once a zone is selected under Zone Management. Between them they write the two things the driver needs to run the zone.
Command Mappings
A command mapping ties one GEM verb to one Z-Wave value plus the value to send.
- Select the Endpoint and then the Command Class value in the section above.
- If the value is writable, enter the Value to send for this verb —
99for a dimmer'son,0for itsoff, and leave it as-is forlevel, where the slider supplies the number. - Pick the Zone Command — the dropdown lists the verbs in this controller's command set that carry the
zonetemplate. - Click Map Command.
Saved mappings appear under Mapped Commands as verb ← Class property value, each with two icons: the remote icon sends that verb to the zone as a live test, and the delete icon removes the mapping after a confirmation.
Numbers and true/false values typed into the Value box are converted to the type the node reported, so the map holds 99 rather than "99". Each saved entry also carries the value's metadata, which is what lets GEM clamp a slider: a level or setpoint argument above the value's own maximum is reduced to that maximum before it is sent. That is what keeps a UI slider sending 100 working against a Multilevel Switch, which tops out at 99 (255 on that class means "restore the last level", not "full").
Response Mappings
A response mapping ties one zone attribute to one Z-Wave value, so the zone reflects what the device reports.
- Select the Endpoint and Command Class value in the section above.
- Enter or pick the Attribute Name —
state,level,temperature,battery, and so on. The list offers every zone attribute name already in use; you can type a new one. - Turn Map Values on and use the Value Mapping editor to translate raw readings into the words the UI expects —
255→on,0→off,23→closed. - Click Map Response.
Saved mappings appear under Mapped Responses with their value translations underneath, each with a delete icon.
Map Response only completes while the Map Values switch is on — with the switch off the save does not go through and no toast appears. If a response mapping never shows up in the list, that is why. Leave the translation table empty if you don't need one; the switch itself is what matters.
How a mapped zone behaves
Commands out
Pressing a button on a UI, running a macro step or firing a trigger sends the verb to the zone. GEM looks the verb up in the zone's command map and writes the mapped value to the mapped Z-Wave value. It also records what it sent on the zone as an attribute named after the verb — a level command leaves a level attribute, an on command leaves an on attribute. Those are a record of the last command, not device feedback; feedback comes from response mappings.
Two error replies tell you exactly what is missing:
missing command map— nothing has been mapped for this zone at all.command not mapped— the zone has a command map, but not for the verb you pressed.
The node comes from the zone, not from the payload. A zone command takes its node and endpoint from the zone's own node_id:endpoint address, so a node_id argument sent alongside is overwritten rather than honoured — a benign zone's command map can never be applied to a different node on the mesh. This holds for every caller, macros and triggers included: the command map being applied belongs to the zone, so aiming it at another node would write one zone's value mapping to different hardware. Ordinary traffic is unaffected: a zone command has no reason to carry a node id.
When a payload does name a different node, the override is logged as "zwave: ignoring caller node_id … is node …" with both node ids. Macro step and script payloads are stored as JSON, so that log line is the practical way to find a saved automation still aiming at the wrong node — search Logging for it after a zone is re-addressed.
Feedback in
Feedback is polled, not pushed. Reports the device sends on its own are logged but do not update the zone. Instead, once the controller connects, GEM walks each zone's response map in turn — one value per second — reads each value from the node, applies the value translation, and writes the result onto the zone attribute when it changed. The pass then repeats after the controller device's status_interval attribute in milliseconds, defaulting to 3000 (3 seconds).
Two consequences worth planning around:
- Add
status_intervalon a large mesh. With one read per second per mapped value, a stick with many mapped zones spends real time on each pass. Raising the interval on the controller device spaces the passes out. - Polling starts only when the controller connects. New response mappings stay dark until the controller reconnects. Finish the whole batch of mappings, then reload the controller device once from the Devices page.
open, close and pulse
open and close do not write a value directly — they are built for garage doors and gates driven by a momentary contact:
- GEM reads the zone's
stateattribute first and acts only when it disagrees with the request, socloseon an already-closed door does nothing. Ifstateis unknown, GEM goes ahead anyway. Mapstatein Response Mappings if you want that check to work. - The pulse itself sends the mapped value, waits, and sends it again. The wait comes from the verb's
delayargument in milliseconds; anything under 2000 is raised to 2000 to give an opener time to settle, and leaving it empty gives no wait at all — always supply a delay. - The zone attribute
normally_closedreverses the order of the two writes.
A shade motor is positioned, not pulsed. Map level for shades, and if the UI needs open/close buttons, set the zone's open_action and close_action attributes to re-dispatch level at 99 and 0. The AI assistant's shade profile below does exactly that.
Bulk node mapping with the AI Assistant
The Mappings tabs above save one entry per round trip — pick a node, refresh its value IDs, pick one, read its metadata, pick the verb, type the value, save; then do the response side. That is the right tool for one odd node. A single dimmer is five of those loops, a multisensor is more, and a typo in a value ID surfaces only as command not mapped when the button is pressed.
For a batch, the AI Assistant has a map_zwave_zones skill — the Z-Wave sibling of map_bacnet_points, map_modbus_points, map_mqtt_topics, map_knx_group_addresses and map_opc_ua_points. Include the nodes first (enrollment above), then describe them:
Map node 12 as the kitchen island dimmer, node 8 as the front door lock, and nodes 14 and 15 as the bedroom multisensors.
It creates (or updates) one zone per node on the controller, builds the node:endpoint address the driver requires (an address without the colon is never controllable), and writes both mapping attributes from a device-class profile:
| Profile | Command class | Commands | Reports |
|---|---|---|---|
switch | Binary Switch (37) | on / off | state |
dimmer | Multilevel Switch (38) | on / off / level | level, state |
fan | Multilevel Switch (38) | on / off / low / medium / high / level | level, state |
shade | Multilevel Switch (38) | level, plus open_action / close_action overrides | level, state |
lock | Door Lock (98) | lock / unlock | state (full lock-mode map), battery |
sensor | — | none (read-only) | from sensors |
What it handles for you:
- Levels are clamped at 99. The Multilevel Switch class tops out at 99 — 255 means "restore last level", not a position — so a UI slider sending 100 still works.
- The shade trap. Native
open/closego through the momentary pulse described above, which would never position a shade motor. The shade profile leaves those verbs out of the command map and wires the zone'sopen_action/close_actionoverrides to re-dispatchlevelat 99 / 0 instead. - Metadata on every command entry. Generated entries always carry the value's metadata, exactly as entries saved from this page do — that is what the clamping relies on.
- Extra telemetry. Any profile can add
sensorsshorthands — battery, temperature, humidity, illuminance, ultraviolet, motion, contact, tamper, leak, smoke, co, and the legacy Binary Sensor forms. Meter (class 50) power/energy must be given explicitly, because its property key is a computed code that differs per device — read it off the node's value list in Command Class Configuration. Battery is included by default on the lock and sensor profiles. - Validation before any write. The device must be a Z-Wave controller, node IDs must be 1-232, and duplicate
node:endpointpairs are rejected in-batch and against existing zones on the controller (two zones on one address means the second silently shadows the first). A mid-batch failure deletes every zone it created and restores every attribute it touched. It also warns when a mapped verb has nozone-template row in the controller's command set — only those rows route into the driver's zone dispatch.
Reloading is deliberately different from the other point-mapping skills. Mapped zones are always reloaded, which is all a command needs — buttons work immediately. The controller itself is not reloaded unless you ask (reload_device), because the response pollers only start when the driver connects, and reloading a Z-Wave controller restarts the serial driver and re-interviews the mesh. The consequence: attribute feedback stays dark until the controller reconnects. Map the whole batch, then reload the controller once.
Ask for a dry run first whenever the assistant inferred profiles from node names — it returns the resolved address, profile, verbs and reports per zone and writes nothing. The cap is 60 zones per call. Inclusion itself stays on this page — the assistant maps nodes that are already on the mesh, it does not pair them. Per-node raw command_map / response_map entries can be passed for anything a profile doesn't describe, and merge over the profile output.
Maintenance commands
The page exposes inclusion, exclusion and soft reset. Everything else the controller can do runs from the controller device's Commands tab: open Devices, click the remote icon on the controller row, pick the controller in Test Device, then click the remote icon on the command row. Commands that take arguments open a form first; replies and device chatter appear in the Device Console below the grid.
| Command | Arguments | What it does |
|---|---|---|
get_nodes | — | Full record for every node: id, name, location, manufacturer, model, ready flag, status, whether it sleeps, whether it is securely paired, beaming support, protocol and firmware versions. |
get_defined_value_ids | node_id | Every value the node exposes — the same list the Command Class dropdown is built from. |
get_value / set_value | node_id, value_id, value | Read or write one value directly. Elevated sessions only — see the note below. |
get_value_metadata | node_id, value_id | Type, range, writability and state table for one value. |
get_endpoints | node_id | Endpoint list with the command classes each one supports. |
get_node_config | node_id | The device configuration file the controller matched to this node. |
refresh_node_info | node_id | Re-interviews the node. Use on a node stuck at Unknown. Elevated sessions only. |
ping_node | address | Round-trip check against a node. |
get_node_neighbors | node_id | Which nodes this one can hear — the practical way to find a routing hole. |
heal_node | node_id | Rebuilds routes for one node. Elevated sessions only. |
start_healing_network / stop_healing_network | — | Rebuilds routes mesh-wide. Run it after adding or moving mains-powered nodes, and expect it to take a while on a large mesh. Elevated sessions only. |
start_inclusion | strategy | Inclusion with an explicit strategy: default, smart_start, insecure, security_s0, security_s2. Elevated sessions only. |
soft_reset_controller | — | Same as the page's Reset button — reconnects to the stick, keeps pairings. Elevated sessions only. |
hard_reset_controller | — | Wipes the stick. Every node must be re-included afterwards. Elevated sessions only. |
Two rows in that list are not finished: refresh_values and poll_value appear in the command set but have no implementation behind them and answer unknown command. Use refresh_node_info to re-read a node, and response mappings for ongoing values.
get_value / set_value name a node and a raw value_id with no zone in between, so no command map confines what they reach — that is the Door Lock write in its most direct form.
Controller lifecycle is gated the same way: start_inclusion, stop_inclusion, start_exclusion, stop_exclusion, start_healing_network, stop_healing_network, heal_node, refresh_node_info, toggle_radio, soft_reset_controller and hard_reset_controller. These change the mesh itself rather than any one zone, and nothing else confines them — the address rule has no address to check when the payload names no node, and most sites leave Elevated off on the controller so ordinary lighting on the same stick stays usable (see the device table above). So the command list is the only thing standing between a guest tablet and a factory reset that unpairs every lock on the mesh.
All of them answer not authorized: this command requires an elevated session for a non-elevated caller (a guest tablet, kiosk or PIN wall panel). Reads — get_nodes, get_endpoints, get_node_config, get_node_neighbors, ping_node, get_value_metadata, get_defined_value_ids — and every mapped zone command are unchanged, as are macros, triggers, schedules and drivers. See Roles → Elevated devices and macros.
node_id is also treated as a hardware address by the central address confinement, so a non-elevated caller cannot aim a command at a node the account has no zone for.
Real-time updates
While a controller is selected, the page listens for controller events and reacts to them:
| Event | What you see |
|---|---|
| node_added | Toast naming the node; the node list refreshes. |
| node_removed | Toast naming the node; the node list refreshes. |
| inclusion_started / inclusion_stopped | Toast; the enrollment buttons swap enabled state. |
| exclusion_started / exclusion_stopped | Toast; the removal buttons swap enabled state. |
| inclusion_failed / exclusion_failed | Error toast; the buttons reset so you can retry. |
| heal_network_done | Toast confirming a network heal finished. |
A heal in progress reports quietly without a toast, so a mesh-wide heal looks silent until it completes.
Troubleshooting
Enrollment fails
- Confirm inclusion is actually running — Start was clicked and the toast appeared.
- Confirm the device is in pairing mode (manufacturer instructions).
- Move the device within about 30 feet of the controller for the initial pairing; you can relocate it afterwards.
- Exclude the device first, then re-enroll — this clears a pairing to a previous controller.
- If the device demands DSK/PIN validation, it will not finish from this page. See the secure-inclusion warning above.
The node list is empty or a node shows as Unknown
- Confirm the controller device is enabled and connected — the address must point at a serial path that exists.
- Use the reload icon in the Z-Wave Nodes header.
- Run
refresh_node_infofor the node. Battery devices must be awake for this to succeed; press the device's button first.
A button does nothing, or the reply is an error
| Reply | Fix |
|---|---|
unknown command | The verb's row in the controller's command set has no zone template. See "The zone template" above. |
missing command map | The zone has no command mappings yet. |
command not mapped | The command map has no entry for that verb — map it in Command Mappings. |
unknown zone address | The zone's address is not a node:endpoint pair on this controller. |
| Nothing at all | Test the value directly with Test Set. If that does not move the load, the mapped value is wrong for the device. |
Values are not updating
- Confirm the attribute is mapped in Response Mappings — feedback only comes from polling those entries.
- Reload the controller device. Response mappings added since the controller last connected are not being polled yet.
- Battery devices only answer while awake, so their attributes update on their own reporting rhythm rather than on the poll cycle.
- Check the value translation under Map Values — a raw reading with no translation is written through as-is, so a UI expecting
on/offwill not light up for255/0. - Confirm the attribute type matches what the device reports.
The device responds slowly or intermittently
- Check
get_node_neighborsfor the node — a node with no mains-powered neighbour is a routing hole. - Run
start_healing_networkafter adding or moving mains-powered devices. - Raise
status_intervalon the controller if a large mesh is being polled continuously. - Soft-reset the controller if communication remains unreliable; the Reset button keeps pairings.
Related Documentation
- Devices - Z-Wave controller device configuration
- Zones - Zone configuration
- Command Sets - Zone command definitions and the
zonetemplate - Attributes - Editing zone and device attributes by hand
- AI Assistant -
map_zwave_zonesbulk node mapping