defmodule Grizzly.ZWave.QRCode do
@moduledoc """
Z-Wave QR code
This module handles Z-Wave QR codes that follow Silicon Labs
Software Design Specification SDS13937 and SDS13944.
"""
alias Grizzly.ZWave
alias Grizzly.ZWave.SmartStart.MetaExtension.UUID16
@typedoc "QR Code version (S2-only or Smart Start-enabled)"
@type version() :: :s2 | :smart_start
@typedoc """
Device information
* `:zwave_device_type` - either {generic_device_type, specific_device_type} or a number
"""
@type t() :: %__MODULE__{
version: version(),
requested_keys: [ZWave.Security.key()],
dsk: ZWave.DSK.t(),
zwave_device_type: {atom(), atom()} | 0..65535,
zwave_installer_icon: ZWave.IconType.name() | ZWave.IconType.value(),
manufacturer_id: 0..65535,
product_type: 0..65535,
product_id: 0..65535,
application_version: 0..65535,
uuid16: nil | UUID16.t()
}
defstruct version: :smart_start,
requested_keys: [],
dsk: ZWave.DSK.zeros(),
zwave_device_type: 0,
zwave_installer_icon: 0,
manufacturer_id: 0,
product_type: 0,
product_id: 0,
application_version: 0,
uuid16: nil
@lead_in "90"
@doc """
Encode device information into Z-Wave QR Code format
The output of this is either a 90 byte or 136 byte string that should be put
into a QR Code. Z-Wave specifies that the 90-byte code (no UUID16) is made
into a 29x29 pixel QR Code. The 136-byte* code (w/ UUID16) should be put into
a 33x33 pixel QR Code.
QR Codes should be encoded as type "text" with error correction "L".
NOTE: SDS13937 has a mistake with the code length. It says 134 bytes, but the
UUID16 encode has 2 extra bytes for presentation, so it should be 136.
"""
@spec encode!(t()) :: iolist()
def encode!(info) do
payload = [
encode_requested_keys(info.requested_keys),
encode_dsk(info),
encode_qr_product_type(info),
encode_qr_product_id(info),
encode_uuid16(info.uuid16)
]
[@lead_in, encode_version(info.version), checksum(payload), payload]
end
defp checksum(payload) do
<<two_bytes::16, _rest::144>> = :crypto.hash(:sha, payload)
int_to_string(two_bytes, 5)
end
defp encode_version(:s2), do: "00"
defp encode_version(:smart_start), do: "01"
defp encode_requested_keys(requested_keys) do
ZWave.Security.keys_to_byte(requested_keys)
|> int_to_string(3)
end
defp encode_dsk(info), do: ZWave.DSK.to_string(info.dsk, delimiter: "")
defp encode_qr_product_type(info) do
# QR Product type = TLV type 00, length 10
[
"0010",
encode_device_type(info.zwave_device_type),
encode_icon_type(info.zwave_installer_icon)
]
end
defp encode_device_type({generic, specific}) do
device_type =
ZWave.DeviceClasses.generic_device_class_to_byte(generic) * 256 +
ZWave.DeviceClasses.specific_device_class_to_byte(generic, specific)
encode_device_type(device_type)
end
defp encode_device_type(device_type) when is_integer(device_type) do
int_to_string(device_type, 5)
end
defp encode_icon_type(type) when is_atom(type) do
{:ok, value} = ZWave.IconType.to_value(type)
encode_icon_type(value)
end
defp encode_icon_type(type), do: int_to_string(type, 5)
defp encode_qr_product_id(info) do
# QR Product ID = TLV type 02, length 20
[
"0220",
int_to_string(info.manufacturer_id, 5),
int_to_string(info.product_type, 5),
int_to_string(info.product_id, 5),
int_to_string(info.application_version, 5)
]
end
defp encode_uuid16(nil), do: []
defp encode_uuid16(uuid16) do
value = UUID16.encode(uuid16)
<<0x06, 0x11, presentation, uuid::binary>> = value
decimalized_uuid =
uuid
|> ZWave.DSK.new()
|> ZWave.DSK.to_string(delimiter: "")
# UUID16 = TLV type 06, length 42
["0642", int_to_string(presentation, 2), decimalized_uuid]
end
defp int_to_string(value, num_digits) when value >= 0 do
value
|> Integer.to_string(10)
|> String.pad_leading(num_digits, "0")
end
end