Skip to content

Instantly share code, notes, and snippets.

@tenderlove
Created February 4, 2018 00:41
Show Gist options
  • Save tenderlove/40ea79896bb66a48c95cf569c5e24240 to your computer and use it in GitHub Desktop.
Save tenderlove/40ea79896bb66a48c95cf569c5e24240 to your computer and use it in GitHub Desktop.
Aeotec Z-Wave Z-Stick Gen 5 UART protocol

Z-Stick Serial API

The Z-Stick does bi-directional communication over a UART. The connection speed is 115200, '8N1'. There are "requests" and "responses". The client software can make requests to the Z-Stick, and it will send responses. But it seems the Z-Stick can make requests of the client software too. I have yet to figure out the requests the Z-Stick sends to the client software.

Packets

There are 4 types of packets, SOF, ACK, NAK, and CAN. SOF packets wrap a request or a response. ACK and NAK are used to acknowledge or reject a packet respectively. CAN means "send again".

SOF requests have a variable number of bytes. ACK, NAK, and CAN are one byte.

SOF requests look like this (they are variable width, but always start with 0x01 and end with a CRC):

        00          04          08          0C         
        -----------------------------------------------
0000    01 01 00 00 00 .....
          |  |  |  | CRC

ACK looks like this (it's just 0x06):

        00          04          08          0C         
        -----------------------------------------------
0000    06

NAK looks like this (it's just 0x15):

        00          04          08          0C         
        -----------------------------------------------
0000    15

I haven't figured out how to use CAN yet, but I think it's just one byte:

        00          04          08          0C         
        -----------------------------------------------
0000    18

Request / Response

Requests and responses are wrapped in a SOF. The second byte is the length of the packet. The third byte is whether the packet is a request or response. Request is 0x00, response is 0x01. The final byte is a CRC.

CRC

The CRC is is an XOR of all bytes except the first SOF and request / response byte, where the CRC starts with 0xFF. Here is pseudo code for calculating the CRC byte:

crc = 0xFF
for byte in bytes
  crc = crc ^ byte
end
return crc

For example, to send 2 bytes 0x01 and 0x04, the length will be 3 (to take in to account the CRC byte) and the bytes list in the above example will be [0x03, 0x01, 0x04], and the CRC will be 0xF9.

Requests

Sample request:

        00          04          08          0C         
        -----------------------------------------------
0000    01 03 00 15 e9
         a  b  c  d  e

a: SOF
b: Length
c: Request
d: Function (In this case "get version" request)
e: CRC

The first byte is SOF, second byte is the length including CRC byte, the third byte indicates if this is a request or a response, the fourth byte indicates the function we are requesting (in this case asking for the version of the Z stick) and the final byte is a CRC.

After sending a properly formed request, the stick will respond with an ACK byte. Eventually it will send a response with a function number that is the same as the function number in the request packet the client software made. The client software must then send an ACK to acknowledge receipt of the response.

Response

Here is a sample response, in response to the above request:

        00          04          08          0C         
        -----------------------------------------------
0000    01 10 01 15 5a 2d 57 61 76 65 20 33 2e 39 35 00
         a  b  c  d [e
0010    01 99
         ]  f

a: SOF
b: Length
c: Response
d: Function (In this case "get version" response)
e: Response data
f: CRC

This sample response packet is a response to a "get version" request. The decoded response data is "Z-Wave 3.95\x00\x01". In this case, the response is a string, and it looks like the stick assumes client applications deal with null padded strings.

Full Request / Response sample

This is a sample of a successful "get version" request. The client software first makes a request, the stick ACKs the request, the stick sends a response, then the client software ACKs the response:

Step 1, Initial request client software:

        00          04          08          0C         
        -----------------------------------------------
0000    01 03 00 15 e9
         a  b  c  d  e

a: SOF
b: Length
c: Request
d: Function (In this case "get version" request)
e: CRC

Step 2, ACK from stick:

        00          04          08          0C         
        -----------------------------------------------
0000    06
         a

a: ACK

Step 3, response from stick:

        00          04          08          0C         
        -----------------------------------------------
0000    01 10 01 15 5a 2d 57 61 76 65 20 33 2e 39 35 00
         a  b  c  d [e
0010    01 99
         ]  f

a: SOF
b: Length
c: Response
d: Function (In this case "get version" response)
e: Response data
f: CRC

Step 4, ACK from client software:

        00          04          08          0C         
        -----------------------------------------------
0000    06
         a

a: ACK

Sample client in Ruby

require 'uart'
require 'io/wait'

SOF = 0x01
ACK = 0x06

REQUEST     = 0x00
RESPONSE    = 0x01
GET_VERSION = 0x15

def crc buf
  buf.inject(0xFF) { |check, byte| check ^ byte }
end

UART.open ARGV[0], 115200, '8N1' do |uart|
  uart.wait_writable
  req = [0x03, REQUEST, GET_VERSION]
  uart.write SOF.chr        # Send SOF
  uart.write req.pack('C3') # Write packet
  uart.write crc(req).chr   # Write CRC

  loop do
    uart.wait_readable
    packet_type = uart.read(1).bytes.first
    case packet_type
    when ACK then puts "Got ACK"
    when SOF then
      len                 = uart.read(1).bytes.first
      packet              = uart.read(len).bytes
      request_or_response = packet.shift

      if request_or_response == RESPONSE
        crc      = packet.pop

        if crc == crc([len, request_or_response] + packet)
          # Send an ACK if the CRC is correct
          uart.write ACK.chr

          function = packet.shift

          if function == GET_VERSION
            puts "Version: #{packet.pack('C*')}"
            break
          else
            puts "Got a non-get version response!"
          end
        else
          puts "Bad CRC!"
        end
      else
        puts "Got a request!"
      end
    else
      puts "Unknown #{sprintf("%02x", packet_type)}"
    end
  end
end

vim: tw=80

@ryanwinchester
Copy link

The CAN frame indicates that the receiving end discarded an otherwise valid Data frame. The CAN frame is used to resolve race conditions, where both ends send a Data frame and subsequently expects an ACK frame from the other end.

| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|-------------------------------|
|          CAN (0x18)           |

If a Z-Wave chip expects to receive an ACK frame but receives a Data frame from the host, the Z-Wave chip SHOULD return a CAN frame. A host which receives a CAN frame MUST consider the dataframe lost. The host MUST wait for a period before retransmitting the Data frame.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment