I’m trying to see if Smart Start is actually easier than running around entering PINs, and so far… not turning out to be. I’m having some trouble with the camera in the app, so instead used a QR code reader to enter the values as text into ZWave To MQTT’s UI (via copy and paste, so I know the number is right). But now I’m seeing
2022-02-09T03:33:58.975Z CNTRLR Received Smart Start inclusion request
2022-02-09T03:33:58.975Z CNTRLR NWI Home ID not found in provisioning list, ignoring request...
When a node broadcasts a SmartStart inclusion request, it includes part of its DSK in the request message. The log message you see means that the DSK advertised does not match any part of a DSK that is in the driver’s node provisioning list. That means it’s an issue with the QR code you added. Either it wasn’t really added (double check it’s still in the list?) or the QR code was not for that device. Also note that if you added the QR code and then excluded the device, the provisioning entry is removed.
Now, to figure out what from the info it’s seeing. It’s got a long serial number, and then
2022-02-09T23:54:51.237Z DRIVER « [REQ] [ApplicationUpdateRequest]
type: SmartStart_HomeId_Received
remote node ID: 0
NWI home ID: 0xce3305d8
basic device class: 4
generic device class: 16
specific device class: 1
supported CCs:
· Z-Wave Plus Info
· Binary Switch
· Association
· Association Group Information
· Version
· Manufacturer Specific
· Transport Service
· Device Reset Locally
· Powerlevel
· Central Scene
· Security 2
· Supervision
· Configuration
· Scene Actuator Configuration
· Scene Activation
· Application Status
· Firmware Update Meta Data
I can find plenty about Zwave “generic device class”, but… only with constant names — how do I see what “16” is? Presumably that’d narrow it down some at least!
No, this message does not contain any serial number.
The closest thing you get is the “NWI home ID” which is derived from the device’s DSK but is not completely reversible. You could take all of your device DSKs and calculate them manually, here’s an example Node JS function.
export function nwiHomeIdFromDSK(dsk: Uint8Array): Uint8Array {
// NWI HomeID 1..4 shall match byte 9..12 of the S2 DSK.
// Additionally:
// • Bits 7 and 6 of the NWI HomeID 1 shall be set to 1.
// • Bit 0 of the NWI HomeID 4 byte shall be set to 0.
const ret = new Uint8Array(4);
ret.set(dsk.subarray(8, 12), 0);
ret[0] |= 0b11000000;
ret[3] &= 0b11111110;
return ret;
}
Or you could generate the 8 possible permutations of possible DSK parts from the NWE Home ID by substituting the different possible bit combinations.
Also need to convert from ASCII DSK to bytes. E.g. this example function will take the full DSK and generate the byte array:
export function dskFromString(dsk: string): Uint8Array {
if (!isValidDSK(dsk)) {
throw new ZWaveError(
`The DSK must be in the form "aaaaa-bbbbb-ccccc-ddddd-eeeee-fffff-11111-22222"`,
ZWaveErrorCodes.Argument_Invalid,
);
}
const ret = new Uint8Array(16);
const view = Bytes.view(ret);
const parts = dsk.split("-");
for (let i = 0; i < 8; i++) {
const partAsNumber = parseInt(parts[i], 10);
view.writeUInt16BE(partAsNumber, i * 2);
}
return ret;
}
Both functions can be called using Z-Wave JS UI’s driver code feature.
I asked an AI to write Python scripts to convert DSK → NWI Home ID, and the reverse, and I think I got working examples. The results match when I compare some of my known DSK.
nwi-to-dsk.py
#!/usr/bin/env python3
import sys
import struct
def hex_to_bytes(hex_str: str) -> bytearray:
"""Convert hex string to bytes, accepting formats with or without '0x' prefix."""
# Remove '0x' prefix and spaces if present
hex_str = hex_str.replace("0x", "").replace(" ", "")
try:
return bytearray.fromhex(hex_str)
except ValueError:
raise ValueError("Invalid hex string")
def get_possible_dsk_bytes(nwi_home_id: bytearray) -> list[bytearray]:
"""Calculate possible DSK bytes 9-12 from NWI Home ID."""
if len(nwi_home_id) != 4:
raise ValueError("NWI Home ID must be exactly 4 bytes")
result = bytearray(nwi_home_id)
# For byte 1, bits 7 and 6 could have been 0 or 1 originally
possible_first_bytes = [
result[0] & 0b00111111, # Original bits were 00
(result[0] & 0b00111111) | 0b01000000, # Original bits were 01
(result[0] & 0b00111111) | 0b10000000, # Original bits were 10
result[0], # Original bits were 11
]
# For the last byte, bit 0 could have been 0 or 1
possible_last_bytes = [
result[3], # Original bit was 0
result[3] | 0b00000001, # Original bit was 1
]
# Generate all possible combinations
possibilities = []
for first_byte in possible_first_bytes:
for last_byte in possible_last_bytes:
possible_result = bytearray([first_byte, result[1], result[2], last_byte])
possibilities.append(possible_result)
return possibilities
def bytes_to_dsk_values(dsk_bytes: bytearray) -> list[str]:
"""Convert 4 bytes into the corresponding 2 DSK values (in ASCII format)."""
# Each pair of bytes becomes one 5-digit value
first_value = struct.unpack(">H", dsk_bytes[0:2])[0]
second_value = struct.unpack(">H", dsk_bytes[2:4])[0]
# Format as 5-digit strings
return [f"{first_value:05d}", f"{second_value:05d}"]
def bytes_to_hex(buffer: bytearray, uppercase: bool = False) -> str:
"""Convert bytes to hex string with '0x' prefix."""
if not buffer:
return "(empty)"
hex_str = "".join(f"{b:02x}" for b in buffer)
if uppercase:
hex_str = hex_str.upper()
return f"0x{hex_str}"
def main():
if len(sys.argv) != 2:
print("Usage: python3 script.py <NWI_HOME_ID>")
print("NWI_HOME_ID format: 4-byte hex value (e.g., '0xC0112233' or 'C0112233')")
sys.exit(1)
try:
nwi_home_id = hex_to_bytes(sys.argv[1])
if len(nwi_home_id) != 4:
raise ValueError("NWI Home ID must be exactly 4 bytes")
possible_values = get_possible_dsk_bytes(nwi_home_id)
print(f"Given NWI Home ID: {bytes_to_hex(nwi_home_id, uppercase=True)}")
print("\nPossible values for DSK segments 5-6:")
for i, value in enumerate(possible_values, 1):
dsk_values = bytes_to_dsk_values(value)
print(f"{i}. {dsk_values[0]}-{dsk_values[1]}")
except ValueError as e:
print(f"Error: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()
dsk-to-nwi.py
#!/usr/bin/env python3
import sys
import struct
def hex_to_bytes(hex_str: str) -> bytearray:
"""Convert hex string to bytes, accepting formats with or without '0x' prefix."""
# Remove '0x' prefix and spaces if present
hex_str = hex_str.replace("0x", "").replace(" ", "")
try:
return bytearray.fromhex(hex_str)
except ValueError:
raise ValueError("Invalid hex string")
def get_possible_dsk_bytes(nwi_home_id: bytearray) -> list[bytearray]:
"""Calculate possible DSK bytes 9-12 from NWI Home ID."""
if len(nwi_home_id) != 4:
raise ValueError("NWI Home ID must be exactly 4 bytes")
result = bytearray(nwi_home_id)
# For byte 1, bits 7 and 6 could have been 0 or 1 originally
possible_first_bytes = [
result[0] & 0b00111111, # Original bits were 00
(result[0] & 0b00111111) | 0b01000000, # Original bits were 01
(result[0] & 0b00111111) | 0b10000000, # Original bits were 10
result[0], # Original bits were 11
]
# For the last byte, bit 0 could have been 0 or 1
possible_last_bytes = [
result[3], # Original bit was 0
result[3] | 0b00000001, # Original bit was 1
]
# Generate all possible combinations
possibilities = []
for first_byte in possible_first_bytes:
for last_byte in possible_last_bytes:
possible_result = bytearray([first_byte, result[1], result[2], last_byte])
possibilities.append(possible_result)
return possibilities
def bytes_to_dsk_values(dsk_bytes: bytearray) -> list[str]:
"""Convert 4 bytes into the corresponding 2 DSK values (in ASCII format)."""
# Each pair of bytes becomes one 5-digit value
first_value = struct.unpack(">H", dsk_bytes[0:2])[0]
second_value = struct.unpack(">H", dsk_bytes[2:4])[0]
# Format as 5-digit strings
return [f"{first_value:05d}", f"{second_value:05d}"]
def format_full_dsk(known_segments: list[str]) -> str:
"""Format a full DSK string with known segments and X's for unknown parts."""
dsk_segments = ["XXXXX"] * 8 # Initialize all segments as masked
# Put the known segments in positions 4 and 5 (0-based index)
dsk_segments[4] = known_segments[0]
dsk_segments[5] = known_segments[1]
return "-".join(dsk_segments)
def bytes_to_hex(buffer: bytearray) -> str:
"""Convert bytes to hex string with '0x' prefix."""
if not buffer:
return "(empty)"
hex_str = "".join(f"{b:02x}" for b in buffer)
return f"0x{hex_str}"
def main():
if len(sys.argv) != 2:
print("Usage: python3 script.py <NWI_HOME_ID>")
print("NWI_HOME_ID format: 4-byte hex value (e.g., '0xC0112233' or 'C0112233')")
sys.exit(1)
try:
nwi_home_id = hex_to_bytes(sys.argv[1])
if len(nwi_home_id) != 4:
raise ValueError("NWI Home ID must be exactly 4 bytes")
possible_values = get_possible_dsk_bytes(nwi_home_id)
print(f"Given NWI Home ID: {bytes_to_hex(nwi_home_id)}")
print("\nPossible DSK values:")
for i, value in enumerate(possible_values, 1):
dsk_values = bytes_to_dsk_values(value)
full_dsk = format_full_dsk(dsk_values)
print(f"{i}. {full_dsk}")
except ValueError as e:
print(f"Error: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()
See if any of your device DSKs match this:
❯ python3 nwi-to-dsk.py 0xd3f4b812
Given NWI Home ID: 0xd3f4b812
Possible DSK values:
1. XXXXX-XXXXX-XXXXX-XXXXX-05108-47122-XXXXX-XXXXX
2. XXXXX-XXXXX-XXXXX-XXXXX-05108-47123-XXXXX-XXXXX
3. XXXXX-XXXXX-XXXXX-XXXXX-21492-47122-XXXXX-XXXXX
4. XXXXX-XXXXX-XXXXX-XXXXX-21492-47123-XXXXX-XXXXX
5. XXXXX-XXXXX-XXXXX-XXXXX-37876-47122-XXXXX-XXXXX
6. XXXXX-XXXXX-XXXXX-XXXXX-37876-47123-XXXXX-XXXXX
7. XXXXX-XXXXX-XXXXX-XXXXX-54260-47122-XXXXX-XXXXX
8. XXXXX-XXXXX-XXXXX-XXXXX-54260-47123-XXXXX-XXXXX
// NWI HomeID 1..4 shall match byte 9..12 of the S2 DSK.
// Additionally:
// • Bits 7 and 6 of the NWI HomeID 1 shall be set to 1.
// • Bit 0 of the NWI HomeID 4 byte shall be set to 0.
The NWI Home ID is bytes 9-12 (4-bytes) the S2 DSK, but it’s modified:
Bits 7 and 6 are set to 1. Their original values are either 0 or 1, you don’t know.
Bit 0 is set to 0. The original value was either 0 or 1, you don’t know.
Thus with the 3 modified bits you have 8 possible permutations of those bits: