Coder Social home page Coder Social logo

grizzly's Introduction

CircleCI Hex.pm

An Elixir library for Z-Wave

Installation

def deps do
  [
    {:grizzly, "~> 5.2"}
  ]
end

Hardware Requirements

The zipgateway binary allows Grizzly to use Z-Wave over IP or Z/IP. Using the zipgateway binary provided by Silicon labs allows Grizzly to support the full range of Z-Wave features quickly and reliability. Some of the more advanced features like S2 security and smart start are already supported in Grizzly.

See instructions below for compiling the zipgateway binary and/or running locally.

If you want a quick reference to common uses of Grizzly see the cookbook docs.

Basic Usage

Grizzly exposes a supervisor Grizzly.Supervisor for the consuming application to add to its supervisor tree. This gives the most flexibility and control over when Grizzly's processes start. Common ways to start Grizzly can look like:

# all the default options are fine
Grizzly.Supervisor.start_link()

# using custom hardware where the serial port is different than the default
# the default serial port is /dev/ttyUSB0.
Grizzly.Supervisor.start_link(serial_port: "/dev/ttyS4")

# if your system is using zipgateway-env and/or something other than Grizzly
# will start and manage running the zipgateway binary
Grizzly.Supervisor.start_link(run_zipgateway: false)

There are other configuration options you can pass to Grizzly but the above are most common options. The Grizzly.Supervisor docs explains all the options in more detail.

To use a device you have to add it to the Z-Wave network. This is "called including a device" or "starting an inclusion." While most of the Grizzly's API is synchronous the process of adding a node is not. So, if you are working from the IEx console you can use flush to see the newly add device. Here's how this process roughly goes.

iex> Grizzly.Inclusions.add_node()
:ok
iex> flush
{:grizzly, :report,  %Grizzly.Report{
  command: %Grizzly.ZWave.Command{
    name: :node_add_status,
    params: [<node info in here>]
  }
}}

To remove a device we have to do an exclusion. Z-Wave uses the umbrella term "inclusions" for both adding a removing a device, but an "inclusion" is only about device pairing and "exclusion" is only about device removal. The way to remove the device from your network in IEx:

iex> Grizzly.Inclusions.remove_node()
:ok
iex> flush
{:grizzly, :report,  %Grizzly.Report{
  command: %Grizzly.ZWave.Command{
    name: :node_remove_status,
    params: [<node info in here>]
  }
}}

There are more details about this process and how to better tie into the Grizzly runtime for this events in Grizzly.Inclusions.

After you included a node it will be given a node id that you can use to send Z-Wave commands to it. Say for example we added an on off switch to our network, in Z-Wave this will be called a binary switch, and it was given the id of 5. Turning it off and on would look like this in IEx:

iex> Grizzly.send_command(5, :switch_binary_set, target_value: :on)
{:ok, %Grizzly.Report{}}
iex> Grizzly.send_command(5, :switch_binary_set, target_value: :off)
{:ok, %Grizzly.Report{}}

For more documentation on what Grizzly.send_command/4 can return see the Grizzly and Grizzly.Report module documentation.

Successful Commands

  1. {:ok, %Grizzly.Report{type: :ack_response}} - normally for setting things on a device or changing the device's state
  2. {:ok, %Grizzly.Report{type: :command}} - this is normally returned when asking for a device state or about some information about a device or Z-Wave network. The command be access by the :command field field of the report.
  3. {:ok, %Grizzly.Report{type: :queued}} - some devices sleep, so sending a command to it will be queued for some amount of type that can be access in the :queued_delay field. Once a device wakes up the calling process will receive the messages in this form: {:grizzly, :report, %Grizzly.Report{}} where the type can either be :queued_ping or :command. To check if the report you receive was queued you can check the :queued field in the report.
  4. {:ok, %Grizzly.Report{type: :timeout}} - the command was sent but for some reason this commanded timed out.

When things go wrong

  1. {:error, :nack_response} - for when the node is not responding to the command. Grizzly has automatic retries, so if you got this message that might mean the node is reachable, your Z-Wave network is experiencing a of traffic, or the node has recently been hit with a lot of commands and cannot handle anymore at this moment.
  2. {:error, :including} - the Z-Wave controller is currently in the inclusion state and the controller cannot send any commands currently
  3. {:error, :firmware_updating} - the Z-Wave controller is currently in the process of having it's firmware updated and is not able to send commands

More information about Grizzly.send_command/4 and the options like timeouts and retries that can be passed to see the Grizzly module.

More information about reports see the documentation in the Grizzly.Report module.

Unsolicited Messages

When reports are sent from the Z-Wave network to the controller without the controller asking for a report these are called unsolicited messages. A concrete example of this is when you manually unlock a lock, the controller will receive a message from the device if the associations are setup correctly (see Grizzly.Node.set_lifeline_association/2 for more information). You can listen for these reports using one of the following functions:

  • Grizzly.subscribe_command/1
  • Grizzly.subscribe_commands/1
  • Grizzly.subscribe_node/1
  • Grizzly.subscribe_nodes/1
Grizzly.subscribe_command(:door_lock_operation_report)

# manually unlock a lock

flush

{:grizzly, :report, %Grizzly.Report{type: :unsolicited}}

To know what reports a device sends please see the device's user manual as these events will be outlined by the manufacture in the manual.

Supported Command Classes

Command Class Version Notes
Alarm (Notification) 8
Anti-theft 3
Anti-theft Unlock 1
Application Status 1
Association 3
Association Group Info 3
Barrier Operator 1
Basic 2
Battery 3
Binary Sensor 2* Partial support, obsoleted by spec
Binary Switch 2
Central Scene 3
Clock 1
Configuration 4
CRC-16 Encapsulation 1
Device Reset Locally 1
Door Lock 4
Firmware Update MD 7
Hail 1
Indicator 4
Mailbox 2
Manufacturer-specific 2
Meter 1
Multi-channel 4
Multi-channel association 4
Multi-command 1
Multilevel Sensor 11* Partial support
Multilevel Switch 4
NM Basic Node 2
NM Inclusion 4
NM Installation and Maintenance 4
NM Proxy 3* Partial support for v4
No Operation 1
Node Naming and Location 1
Node Provisioning 1
Powerlevel 1
Scene Activation 1
Scene Actuator Configuration 1
Schedule Entry Lock 3
Security 1* Partial support
Security 2 1* Partial support
Sound Switch 2
Supervision 2
Thermostat Fan Mode 1
Thermostat Fan State 2
Thermostat Mode 2
Thermostat Operating State 1
Thermostat Setback 1
Thermostat Setpoint 3* Partial support
Time Parameters 1
Time 2
User Code 2
Version 3
Wake Up 3
Window Covering 1
Z/IP Gateway 1* Partial support
Z/IP 5
Z-Wave Plus Info 2

Get Started

Quick and Fast running locally

If you want to run Grizzly locally for development and/or learning before going through the challenge of compiling and running in Nerves we recommend the zipgateway-env project. This provides a docker container and CLI for compiling and running different versions of zipgateway.

Nerves Devices (WIP)

First download the Z/IP GW SDK from Silicon Labs. You'll need to create an account with them to do this, but the download is free.

The default binaries that come with the download will not work by default in Nerves system, so you will need to compile the source for your target. The source code can be found in the Source directory.

This can be tricky and the instructions are a work in progress, so for now please contact us if you any troubles.

Connecting zipgateway to Grizzly

zipgateway runs as a separate server, accessed over a DTLS (UDP over SSL) connection. Grizzly will automatically start this server. It assumes the executable is in /usr/sbin/zipgateway. If this is not the case, you can specify the actual location with

config :grizzly,
  zipgateway_path: "«path»"

Grizzly uses the taptun module to manage the TCP connection: it checks that this is loaded as it starts.

Configuring zipgateway

The zipgateway binary is passed a configuration file named zipgateway.cfg. This has configuration parameters around networking and setting device specific information. Most of these configuration settings are static, so Grizzly can handle those for you in a reliable way. However, there are few exposed configuration options to allow some customization around device specific information, logging, and network interface set up.

Supported configuration fields are documented in t:Grizzly.Supervisor.arg/0.

For the most part if you are using Grizzly to run zipgateway the defaults should just work.

When going through certification you will need provide some device specific information:

config :grizzly,
  zipgateway_cfg: %{
    manufacturer_id: 0,
    product_type: 1,
    product_id: 1,
    hardware_version: 1
  }

The manufacturer_id will be given to you by Silicon Labs, and will default to 0if not set (this is zipgateway level default).

The above fields have no impact on the Grizzly runtime, and are only useful for certification processes.

When running zipgateway binary out side of Grizzly this configuration field is ignored and you will need to pass in the location to your configuration like so:

zipgateway -c /path/to/zipgateway.cfg

Virtual devices

Grizzly provides a way to work with virtual devices. These devices should work as their hardware counterparts, however, the state of these devices are held in memory.

Grizzly provides to example virtual devices:

  1. Grizzly.VirtualDevices.Thermostat
  2. Grizzly.VirtualDevices.TemperatureSensor

To use These virtual devices you will have to start them using the start_link/1 call. These are not supervised by Grizzly, so you will need to add them to your supervision tree. The reason for this is to provide the maximum flexibility to the consumer application in terms of how processes are started and supervised.

After starting the device you can call the Grizzly.send_command/4 function to send the device a command.

{:ok, _pid} = Grizzly.VirtualDevices.Thermostat.start_link([])
{:ok, [{:virtual, _id} = virtual_device_id]} = Grizzly.Network.get_all_node_ids()

Grizzly.send_command(virtual_device_id, :thermostat_setpoint_get)

Virtual device ids are tuples where the first item is the atom :virtual and the second item an integer of the device id, for example: {:virtual, device_id}. Grizzly provides a guard and a helper function for any checking you might have to do:

  1. Grizzly.is_virtual_device/1 (guard)
  2. Grizzly.virtual_device?/1 (function)

Also, the documentation for functions in Grizzly.Node and Grizzly.Network should indicate if they work with virtual devices.

Resources

grizzly's People

Contributors

bjyoungblood avatar dependabot[bot] avatar esvinson avatar fhunleth avatar grace-in-wonderland avatar jellybob avatar jfcloutier avatar jjcarstens avatar jwdotjs avatar livinabsurdism avatar mattludwigs avatar pragdave avatar ryanwinchester avatar stephanfenton avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

grizzly's Issues

Better Errors

Currently Grizzly explodes loudly when we are unable to parse binary correctly. This is useful for local development but not ideal for real-world applications, as we have learned that even if a device passes spec they might send bytes that are slightly outside of the speciation. If this is done quickly enough and we crash too many times to fast this will hit the max restarts and will cause the entire application to crash until a reboot takes place.

We should pass decode errors to the caller so that they can decide on what to do with the error.

Proposal: Change command behaviour

I think to provide a better abstraction around the Grizzly.Command behaviour would be very useful as most active development will be supporting new commands. Right now there is a lot of well defined repetitive code to support new commands that we abstract, and some boundaries get blurred.

I think to do this well there is a multistep process and would be a breaking change to the API that would only affect those who have implemented their own custom commands.

First, we should move seq_number and retires to a higher level where there are more runtime concerns: Grizzly.Command. However, that means we will still need to pass retries from the concrete command. We should change Grizzly.Command.encode/1 callback to be Grizzly.Command.encode/2 where the second argument is the seq_number. Retry logic and be at the command process level then.

I think to make the macro system most friendly we will want to separate out Grizzly.Command.handle_response callback to thave handle_ack, handle_nack, handle_nack_waiting (maybe not the last one). This will allow us to do better documentation and provide defaults in a __using__ macro.

After this point I can see using generators or developing a DSL for defining a command depending where the library is at the point of getting the above changes in.

This is a multi PR process, and I am open to suggestions.

Question: When is Grizzly "ready"?

When nerves boots, I want a GenServer to start in my supervision tree that calls Grizzly.get_nodes/0, but when can I make sure this is ready?

Is Grizzly.network_ready?/0 good for this?

Refactor and Migration

I have been thinking about refactoring Grizzly for a very long time now. However, migrating and ensuring that we don't break something in a catastrophic way is a challenge.

Some high-level ideas of the refactor:

Move away from Grizzly.send_command/4 as the main API call

I think we still expose it but it will ultimately change a bit. I think a better approach would be to provide "higher-level" modules that wrap Grizzly.send_command where the API is the commands the command class can send. To start we can just leave most things as is by just wrapping what is currently there. However, by exposing a new API we can slowly migrate implementation details without breaking everything at once.

However, these higher-level APIs would just call into a Grizzly.send_command still, so the current philosophy of Grizzly (minimize sid-effect causing functions) would still be maintained.

Remove Grizzly.ZWave.Commands.* namespace

Basically, the end game here to delete all the modules for each command. Rather I think we should move them into the command class level module under Grizzly.ZWave.CommandClass.* and have them just be functions that output the right command structure. This will delete tons of code and bring similar things into one module. Hopefully, with those two things, we can get better maintaining and readability within the code and an overall smaller codebase.

Refactor Grizzly.ZWave.Command.t()

I think that structure should be

%{
  name: atom(),
  encode: (command.t() -> iodata),
  response: (binary() -> command.t()) | nil,
  params: keyword()
}

This will allow us to delete Grizzly.ZWave.Command module and behavior (as we are moving away from all the command modules). Moreover, the :response field will be either a function that outputs some of the command response expected by sending the command or nil. First, I use to think that what ought to be a response to a particular commadn should be separate from the command itself, however, the specification actually ties these together so I think it is okay that we do too. Also, when the value is nil the command does not expect a response.

Moreover, I think Grizzly.send_command/4 will turn into Grizzly.send_command(node_id(), command.t()) so that others can develop their own commands and send them without introducing behaviors or protocols.

Update docs to group modules

The docs currently are really overwhelming. I think that once we start moving things out we can group the Z-Wave specification stuff and Grizzly stuff separately - which should help users dig into the documentation. Right now answering the question "How do I turn on a light?" isn't really answerable in the docs outside the cookbook or without some background knowledge in Z-Wave.

Clean up some code along the way

This refactor will force some adjustments to the runtime side of Grizzly that I think are much needed but currently are hard to touch. For example, the command lifecycle will need to be updated to use the command.t() structure (which I think will be simpler, but that is hard to prove out right now). Also, inclusions and exclusions can be cleaned up a bit - even right now I think there are hard-to-find bugs hanging out in that code - not to mention the API is a bit awkward.

Why not

First, there is a solid amount of code relying on the current version of Grizzly. However, I think a slow migration is possible.

With the slow migration, we will be developing new code along with support and maybe even develop on some older code at the same time. There will be a bit of bloat due to this, but I think that can be minimized and the end result will be leaner, easier to maintain, and a more user-friendly Grizzly.

Also, when doing a slow migration there will be two ways of sending commands so documentation and user codebases might look a bit funny for a bit.

Summary

I think moving in this direction would benefit the code base and users of Grizzly. I think it can be iterative and a slow migration that can pose little risk. We can deprecate the old way as the new way is supported and allow time for updates. Part of me thinks this might put us on the path to a v1.0.0 release because the API will start to feel solidified, but that is just me daydreaming right now.

For an example of the first baby steps towards this see #477. Basically, this just wraps the current Grizzly.send_command.

Support passing node id to send_commnad

Currently send command requires a connected thing (Conn, Node, Controller) to run which is really good for systems that will need to do variable timed, automated command sending and non-deterministic, long-running systems, but for quick one-off commands or testing in iex, it can be less idea from an API standpoint.

We should support passing in a node id to the send_command function which will connect to the node, send the command, wait for the response, then close the DTLS connection. We should document the use case for this function well as I would assume most applications will connect to the node and keep the DTLS connection alive.

Update checking for when zipgateway is ready

The next release of the Z-Wave spec says zipgateway will notify the unsolicited destination of the node list when zipgateway is ready.

This is already happening but is just becoming official. So, we should update how Grizzly notifies the consumer to use this rather than polling the controller by trying to connect to it. This should improve some startup times.

One thing to test is which versions of zipgateway do this to see if we need to maintain backward capability with older versions.

User Code GET validation

According to the Z-Wave spec running a UserCode.Get with the slot id equal 0 is invalid, we should check that the slot_id is not 0.

If it returns an error tuple with and EncodeError.t() with the message saying that slot id cannot equal 0. For now, this can happen at the UserCode.Get.encode/1 level.

Stop firmware update if crash

If the Grizzly.FirmwareUpdates.FirmwareUpdateRunner crashes during inclusion the terminate/2 callback should issue a command to the Z-Wave controller to stop the firmware update process.

See #417 for more information.

Grizzly send_command refactor

We should change the current send_command API to be:

Grizzly.send_command(node_id, :switch_binary_set, value: :on)

This is will make all public-facing command names atoms. No more modules. This will push Grizzly's command and command class names to be more consistent in their type. We can still provide extensibility like so:

Grizzly.send_command(node_id, :switch_binary_set, [value: :on], handle: MyCommandHandle)

The current Command bahviour will become the CommandHandle behaviour. This will also make naming easier internally but not having a Z-Wave command and Grizzly command. However, I think implementing this can wait until the refactoring is stable.

The reason I am preferring send_command/4 for the last options is that the last options can be connection/send_command opts and the first keyword list is strictly the command params. I think the added opts while more code both makes the implementation code more simple, and the API easier to understand, document, and type (so that Dialyzer is still useful).

One thing that can be a downfall to this refactor is right now with the Command behaviour we easily document the params the command takes and can use the help utility in iex. I think this can be solved because we can still have modules for each command and/or command class that will have the encode/decode logic and data structures.

This also means, at least to start with, people are forced down our encoding and decoding logic, which with refactoring how commands work I don't think is a bad thing. I think that there are ways around this long term and probably more useful then what we have today, but I don't this is an immediate need.

With such a nuanced and large protocol such as Z-Wave, I think it is okay that we control more and provide a useful way for people to use Grizzly. I think if someone is implementing some of these details they will have some deeper knowledge can provide back to Grizzly directly. I think I went too fast to extensibility, which has lead to some awkward things in Grizzly.

Aggregate Report Check Name

Currently, the aggregate report handler does not guard against the name of the incoming report: https://github.com/smartrent/grizzly/blob/main/lib/grizzly/command_handlers/aggregate_report.ex#L31.

This can cause issues when the same node has a different command sent the same time.

iex(15)> thermostat_id=22
22
iex(16)> Grizzly.send_command(thermostat_id, :association_get)
** (exit) exited in: GenServer.call({:via, Registry, {Grizzly.ConnectionRegistry, 22}}, {:send_command, %Grizzly.ZWave.Command{command_byte: 2, command_class: Grizzly.ZWave.CommandClasses.Association, impl: Grizzly.ZWave.Commands.AssociationGet, name: :association_get, params: []}, 22, [handler: {Grizzly.CommandHandlers.AggregateReport, [complete_report: :association_report, aggregate_param: :nodes]}]}, 140000)
    ** (EXIT) exited in: GenServer.call(#PID<0.3282.0>, {:handle_zip_command, %Grizzly.ZWave.Command{command_byte: 2, command_class: Grizzly.ZWave.CommandClasses.ZIP, impl: Grizzly.ZWave.Commands.ZIPPacket, name: :zip_packet, params: [seq_number: 54, source: 0, dest: 0, secure: false, header_extensions: [encapsulation_format_info: [:non_secure]], command: %Grizzly.ZWave.Command{command_byte: 18, command_class: Grizzly.ZWave.CommandClasses.Version, impl: Grizzly.ZWave.Commands.VersionReport, name: :version_report, params: [library_type: :routing_slave, protocol_version: "4.5", firmware_version: "1.0", hardware_version: 1, other_firmware_versions: ["2.16"]]}, flag: nil]}}, 5000)
        ** (EXIT) an exception was raised:
            ** (KeyError) It looks like you tried to get the :reports_to_follow from your command.
Here is a list of available params for your command:
  * :library_type
  * :protocol_version
  * :firmware_version
  * :hardware_version
  * :other_firmware_versions
                (grizzly 0.14.4) lib/grizzly/zwave/command.ex:87: Grizzly.ZWave.Command.param!/2
                (grizzly 0.14.4) lib/grizzly/command_handlers/aggregate_report.ex:31: Grizzly.CommandHandlers.AggregateReport.handle_command/2
                (grizzly 0.14.4) lib/grizzly/commands/command.ex:171: Grizzly.Commands.Command.do_handle_zip_command/2
                (grizzly 0.14.4) lib/grizzly/commands/command_runner.ex:64: Grizzly.Commands.CommandRunner.handle_call/3
                (stdlib 3.13) gen_server.erl:706: :gen_server.try_handle_call/4
                (stdlib 3.13) gen_server.erl:735: :gen_server.handle_msg/6
                (stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
    (elixir 1.10.4) lib/gen_server.ex:1023: GenServer.call/3

If the report that is coming in this handler is different then the expected one we should return {:continue, state}

Handle <<130, 1>> from a Z-Wave device

Here's the log message:

11:13:01.537 [error] GenServer #PID<0.5004.0> terminating
** (FunctionClauseError) no function clause matching in Grizzly.ZWave.Decoder.from_binary/1
    (grizzly 0.12.1) lib/grizzly/zwave/decoder.ex:1: Grizzly.ZWave.Decoder.from_binary(<<130, 1>>)
    (grizzly 0.12.1) lib/grizzly/zwave/commands/zip_packet.ex:80: Grizzly.ZWave.Commands.ZIPPacket.decode_params/1
    (grizzly 0.12.1) lib/grizzly/zwave/decoder.ex:1: Grizzly.ZWave.Decoder.decode/2
    (grizzly 0.12.1) lib/grizzly/unsolicited_server/messages.ex:42: Grizzly.UnsolicitedServer.Messages.broadcast/2
    (grizzly 0.12.1) lib/grizzly/unsolicited_server/socket.ex:48: Grizzly.UnsolicitedServer.Socket.handle_info/2
    (stdlib 3.13) gen_server.erl:680: :gen_server.try_dispatch/4
    (stdlib 3.13) gen_server.erl:756: :gen_server.handle_msg/6
    (stdlib 3.13) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: {:ssl, {:sslsocket, {:gen_udp, {#PID<0.2754.0>, {{{64768, 48059, 0, 0, 0, 0, 0, 41}, 41230}, #Port<0.151>}}, :dtls_connection}, [#PID<0.12318.0>]}, <<35, 2, 0, 192, 38, 0, 0, 5, 132, 2, 0, 0, 130, 1>>}
State: %Grizzly.UnsolicitedServer.Socket.State{listen_socket: {:sslsocket, nil, {:dtls, {:config, %{middlebox_comp_mode: true, cert: :undefined, cacerts: :undefined, signature_algs: :undefined, keyfile: "", crl_check: false, verify_fun: {#Function<12.9303974/3 in :ssl.handle_options/3>, []}, alpn_preferred_protocols: :undefined, password: [], sni_fun: :undefined, max_fragment_length: :undefined, fail_if_no_peer_cert: false, cb_info: {:gen_udp, :udp, :udp_close, :udp_error, :udp_passive}, log_level: :notice, sni_hosts: [], eccs: {:elliptic_curves, [{1, 3, 132, 0, 39}, {1, 3, 132, 0, 38}, {1, 3, 132, 0, 35}, {1, 3, 36, 3, 3, 2, 8, 1, 1, 13}, {1, 3, 132, 0, 36}, {1, 3, 132, 0, 37}, {1, 3, 36, 3, 3, 2, 8, 1, 1, 11}, {1, 3, 132, 0, 34}, {1, 3, 132, 0, 16}, {1, 3, 132, 0, 17}, {1, 3, 36, 3, 3, 2, 8, 1, 1, 7}, {1, 3, 132, 0, 10}, {1, 2, 840, 10045, 3, 1, 7}, {1, 3, 132, 0, 3}, {1, 3, 132, 0, 26}, {1, 3, 132, 0, 27}, {1, 3, 132, 0, 32}, {1, 3, 132, 0, 33}, {1, 3, 132, 0, 24}, {1, 3, 132, 0, ...}, {1, 3, 132, ...}, {1, 2, ...}, {1, ...}, {...}, ...]}, erl_dist: false, dhfile: :undefined, secure_renegotiate: true, supported_groups: {:supported_groups, [:x25519, :x448, :secp256r1, :secp384r1]}, key: :undefined, cacertfile: "", customize_hostname_check: [], handshake: :full, beast_mitigation: :one_n_minus_one, psk_identity: "Client_identity", renegotiate_at: 268435456, padding_check: true, honor_ecc_order: false, honor_cipher_order: false, dh: :undefined, next_protocols_advertised: :undefined, fallback: :undefined, max_handshake_size: 262144, reuse_sessions: true, depth: 1, srp_identity: :undefined, user_lookup_fun: {#Function<0.123876537/3 in Grizzly.UnsolicitedServer."-fun.user_lookup/3-">, <<18, 52, ...>>}, session_tickets: :disabled, client_renegotiation: true, ciphers: [<<...>>], versions: [...], ...}, [{:ifaddr, {64768, 43690, 0, 0, 0, 0, 0, 2}}, :inet6, {:active, false}, {:mode, :binary}], [active: true, mode: :binary, packet: 0, packet_size: 0], :undefined, {#PID<0.2754.0>, 41230}, [{:ifaddr, {64768, 43690, 0, 0, 0, 0, 0, 2}}, :inet6, {:active, false}, {:mode, :binary}], {:gen_udp, :udp, :udp_close, :udp_error, :udp_passive}, :dtls_connection}}}}

Unsolicated Messages Updates

Currently receiving commands from Z-Wave forces a user to have a process to handle unsolicited messages from the Z-Wave network. It would be nice to allow passing a module that implements a behavior to handle unsolicited messages and we can provide a runtime for that module.

Add support for access control notification 0x21

When the next Z-Wave specification is released (Fall 2020) we need to update to support a new access control notification.

The change should go right below this line is:

{0x06, :access_control, 0x21, :lock_unlock_operation_with_user_code}

Handle bad encoding of command

Currently, we just crash if the command cannot be encoded. We should allow the call site to decided what to do if command encoding does not work. I think we should change the Command behavior to return an expectation %Grizzly.Command.EncodeError{} wrapped in the :error tuple, and pass that error down to call site. We should also make the command process that is handling the command is stopped.

Compiling zipgateway

I'm trying to find some instructions on how to compile the zipgateway for nerves. I haven't had any luck on finding those instructions.

I'm trying to compile them for the rpi3 target. How do I go about doing that?

Better Queued report handling

Current Grizzly kinda forces the caller to use a GenServer to make calls out to Z-Wave due to the way queued commands are handled. It would be nice to allow for a Queued handler of some type to allow the caller to not be forced into a GenServer.

Node Remove Error

When removing a Z-Wave LR node this happened:

02:26:48.506 [error] GenServer {Grizzly.ConnectionRegistry, {:async, 1}} terminating
** (FunctionClauseError) no function clause matching in Grizzly.ZWave.Commands.NodeRemoveStatus.decode_params/1
    (grizzly 0.18.2) lib/grizzly/zwave/commands/node_remove_status.ex:48: Grizzly.ZWave.Commands.NodeRemoveStatus.decode_params(<<145, 6, 10, 0, 0>>)
    (grizzly 0.18.2) lib/grizzly/zwave/decoder.ex:1: Grizzly.ZWave.Decoder.decode/2 
    (grizzly 0.18.2) lib/grizzly/zwave/commands/zip_packet.ex:80: Grizzly.ZWave.Commands.ZIPPacket.decode_params/1
    (grizzly 0.18.2) lib/grizzly/zwave/decoder.ex:1: Grizzly.ZWave.Decoder.decode/2
    (grizzly 0.18.2) lib/grizzly/transports/DTLS.ex:121: Grizzly.Transports.DTLS.handle_ssl_message_with_ip/4
    (grizzly 0.18.2) lib/grizzly/connections/async_connection.ex:152: Grizzly.Connections.AsyncConnection.handle_info/2
    (stdlib 3.14) gen_server.erl:689: :gen_server.try_dispatch/4
    (stdlib 3.14) gen_server.erl:765: :gen_server.handle_msg/6
Last message: {:ssl, {:sslsocket, {:gen_udp, {{{64768, 48059, 0, 0, 0, 0, 0, 1}, 41230}, #Port<0.201>}, :dtls_gen_connection}, [#PID<0.2067.0>]}, [35, 2, 0, 208, 2, 0, 0, 5, 132, 2, 4, 0, 52, 4, 145, 6, 10, 0, 0]}

Move `Grizzly.Packet` to `Grizzly.CommandClass.ZIPPacket`

Right now the Grizzly.Packet is a kinda half representation of the ZIP_PACKET command. This would involve first support the ZIP_PACKET command, and then having the rest of the library use ZIP_PACKET instead of Grizzly.Packet.

This should be a multipart PR.

Support Sending Binary

Add Grizzly.send_binary(Node.t() | Node.node_id | Controller, binary, seq_number) :: binary() to allow for sending raw binary strings to be sent.

We should do too much in the way of handling acks and nacks to start with and users can use the log to see what is happening. This will be helpful debugging and development.

Handle :ssl.listen crashes more gracefully

The following line:

https://github.com/smartrent/grizzly/blob/master/lib/grizzly/unsolicited_server.ex#L29

returns errors and crashes. It seems like the following crashes are a normal occurrence on start:

23:38:16.691 [error] GenServer Grizzly.UnsolicitedServer terminating
** (MatchError) no match of right hand side value: {:error, {:shutdown, {:error, :closed}}}
    (ssl) dtls_socket.erl:39: :dtls_socket.listen/2
    (ssl) ssl.erl:556: :ssl.listen/2
    (grizzly) lib/grizzly/unsolicited_server.ex:29: Grizzly.UnsolicitedServer.handle_info/2
    (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: :listen
State: %Grizzly.UnsolicitedServer.State{config: %Grizzly.UnsolicitedServer.Config{ip_address: {64768, 43690, 0, 0, 0, 0, 0, 2}, ip_version: :inet6}, socket: nil}

Since the handling of trying a short time later seems to be the right thing to do, can this be wrapped in a try block to so that it doesn't propagate up the supervision tree?

CRC

We don't need to rely on the CRC package as most of it is not really needed for what we do in Grizzly.

Below is the example code to bring into Grizzly and replace the CRC package.

defmodule NewCRC2.Table do
  use Bitwisedef ccitt_table(poly) do
    table =
      for i <- 0..255 do
        ccitt_entry(i <<< 8, 0, 0, poly)
      end
​
    List.to_tuple(table)
  enddefp ccitt_entry(_, crc, 8, _), do: crc &&& 0xFFFFdefp ccitt_entry(c, crc, bc, poly) do
    next_crc = if (crc ^^^ c &&& 0x8000) > 0, do: (crc <<< 1) ^^^ poly, else: crc <<< 1ccitt_entry(c <<< 1, next_crc, bc + 1, poly)
  end


defmodule NewCRC2 do
  @compile :native
  @compile {:hipe, [:o3]}use Bitwise@table NewCRC2.Table.ccitt_table(0x1021)@type uint16 :: 0..65535@doc """
  CRC-16/AUG-CCITT
  """
  @spec crc16_aug_ccitt(binary() | [byte()]) :: uint16
  def crc16_aug_ccitt(data) when is_binary(data) do
    data
    |> :binary.bin_to_list()
    |> crc16_aug_ccitt()
  enddef crc16_aug_ccitt(data) when is_list(data) do
    crc_ccitt(data, 0x1D0F)
  enddefp crc_ccitt([x | rem], crc) do
    index = (crc >>> 8) ^^^ x
    crc = (crc <<< 8) ^^^ elem(@table, index) &&& 0xFFFF
    crc_ccitt(rem, crc)
  enddefp crc_ccitt([], crc), do: crc
end

Timeouts

It seems that setting timeouts on a Grizzly.send_command does not adhere to the timeout time limit.

Only let UserCode.Set with slot id 0 have a zeroed out code

When you want to reset all user codes on a lock you send UserCode.Set with slod_id: 0 and the "zeroed out" code which is: [0, 0, 0, 0, 0, 0, 0, 0].

According to the spec the only time using slot_id 0 is valid is when you using that user code.

We should validate this is true at the UserCode.Set.encode/1 level and if not return the {:error, EncodeError.t()} with a helpful error message explaining this.

spec ref: SDS13781 Z-Wave Application Command Class section 4.113.2

Make zipgateway binary path configurable

Right now the zipgateway path is hardcoded in favor of working with Nerves systems.

Let us make zipgateway_bin configuration with the default to what is currently there.

Not an issue, more a question

I got zip_gateway to build on OS X, and I'm trying to get Grizzly to start up.

Right now, I'm blocked at the point where it does a modprobe for tun.

Is this a fool's errand? Should I just move over to working on my NUC? Or do I stand a chance of getting this to run on OS X?

Cheers

Dave

Fix Keep Alive Timing

According to SDS13784 Z-Wave Network-Protocol Command Class Specification.pdf section 4.11.2:

This command SHOULD be issued at a minimum interval of 25 seconds and at a maximum interval of 55 seconds after the last sent or received a command in order to prevent session closure.

Right now we blindly send the keep-alive interval at 25 seconds which means we can be sending things less than 25 seconds when we send a command.

We should update the connection server to only send the keep-alive when a configured amount of time has passed.

[Question] Notifications

I subscribed to all notifcations, but the only ones I have seen come in so far is connection_established and unsolicited_message under certain conditions.

I have sent commands X.Get, X.Set, but no do get any notifications.

If I manually interact with the Z-Wave device (e.g. turn a switch off) I get unsolicited_message notifications.

Is this because the Z-Wave controller does not bother sending messages (that we have notifications for) to the caller in response to a command? Or something else?

Un-decodable 0x09 network_management_installation_maintenance command

I just saw this

19:44:57.314 [error] GenServer #PID<0.2950.0> terminating
** (FunctionClauseError) no function clause matching in Grizzly.ZWave.Decoder.from_binary/1
    (grizzly 0.15.6) lib/grizzly/zwave/decoder.ex:1: Grizzly.ZWave.Decoder.from_binary(<<103, 9, 8, 0>>)
    (grizzly 0.15.6) lib/grizzly/zwave/commands/zip_packet.ex:80: Grizzly.ZWave.Commands.ZIPPacket.decode_params/1
    (grizzly 0.15.6) lib/grizzly/zwave/decoder.ex:1: 

What's interesting is that I cannot find a documented command 0x09 for CC 0x67 (:network_management_installation_maintenance)

Notification Value

I may be missing something but I want to subscribe to Grizzly notifications so that when a node's state changes I can act on it.

I'm subscribing via:

Grizzly.subscribe(:node_updated)

I get these messages when I turn on the node (via hitting the physical switch):

{:unsolicited_message, %{command_class: :hail, node_id: 27, value: ""}}
{:unsolicited_message, %{command_class: :hail, node_id: 27, value: ""}}

I also get the same above messages when I turn off the switch.

I'm using a Leviton DZ15S-1BZ Decora Smart Switch

I would expect the value to be on or off.

Mix task to generate the zipgateway.cfg

When Grizzly does not run zipgateway automatically and that binary is run separately from Grizzly we need a way to produce a cfg file that allows zipgateway and Grizzly to correctly communicate.

mix grizzly.gen.zipgateway_cfg

This should just output a file named zipgateway.cfg to the calling directory.

Still sending keep alive for node that has been removed from Controller

I had a node on the network and then ran Grizzly.Network.reset_controller() which cleared everything.

But zipgateway is still handling keep alive ACK from the removed nodes

87029185 Sending DISCOVER
87029185 DHCP pass completed
87029185 update flag 0x1 0x85
87030012 node_input_queued: Packet for DTLS_PORT uip_len: 61
87030012 ClassicZIPNode_dec_input: pkt: 23 3 len: 3
87030012 Sending keep alive ACK from z-wave node 11 to port:40522 of IP:fd00:aaaa::2dcc:f922:934:2e64

Runtime error when listing commands for a command class

iex(5)> Grizzly.commands_for_command_class(Grizzly.ZWave.CommandClasses.Configuration)
** (UndefinedFunctionError) function Grizzly.ZWave.Commands.DoorConfigurationSet.new/1 is undefined (module Grizzly.ZWave.Commands.DoorConfigurationSet is not available)
    Grizzly.ZWave.Commands.DoorConfigurationSet.new([])
    (grizzly 0.15.11) lib/grizzly.ex:261: anonymous fn/2 in Grizzly.commands_for_command_class/1
    (elixir 1.11.2) lib/enum.ex:3369: Enum.filter_list/2
    (grizzly 0.15.11) lib/grizzly.ex:260: Grizzly.commands_for_command_class/

When trying to list the commands for the Configuration command class this runtime error happens

Extra Command Support - Z-Wave certification

To pass Z-Wave certification we need to support a handeful of command classes.

  • Device Reset Locallly (SDS13682 Z-Wave Management Command Class Specification)
  • Multi Channel (v3 or above) (SDS13683 Z-Wave Transport-Encapsulation Command Class Specification)
  • Association (v2 or above) (SDS13682 Z-Wave Management Command Class Specification)
    • Association Remove
    • Association Specific Group Get
    • Association Specific Group Report
  • Supervision (SDS13683 Z-Wave Transport-Encapsulation Command Class Specification)
  • Multi Command (SDS13683 Z-Wave Transport-Encapsulation Command Class Specification)
  • Association Grouping Info (SDS13682 Z-Wave Management Command Class Specification)

The sub checkboxes for Association command classes are the commands needed to get full v2 support. The others if they are not already marked as complete need the entire command set for the command class done in Grizzly.

Although this is all in one issue, PRs that break this down to smaller pieces are welcomed.

Mark command class security

When we get a node_cache_info_report we currently ignore the "mark" bytes that separate out the security level fo the command class.

Proposal

Add a field security to the Grizzly.CommandClass struct. That is one of the following: :none, :s0, :s2_unauthenticated, :s2_authenticated, or :s2_access_control.

And provide some type of function to access that field in the module.

I am open to other suggestions.

See SDS13784 Z-Wave Network-Protocol pdf section 4.4.4 provided by SilconLabs for more information.

Refactor connecting to Nodes

I think removing the need to explicitly to connect to nodes would be super beneficial.

When Grizzly starts we should establish a connection to all the nodes and remove the conn field from Grizzly.Node and other connection-related fields.

We are not trying to manage too much state here, so not a cache, only a process registry that maps node ids to conn processes.

So, that way Grizzly.send_command(node_id, command) speeds up on the first command.

Also, Grizzly.send_command(node) will just need to pull the node's id out and send it along.

We will need to handle when a node leaves and joins the network.

When a device is reset locally there should be an unsolicited message about that event and we should close the connection.

When a connection closes for whatever reason, we should handle trying to reconnect.

The main benefit of this is removing the idea of "connecting" to a node and making Grizzly abstract the DTLS layer more.

Implement Polling

With zipgateway 7.14.01 the internal zipgateway support for rating limiting clients has been removed for polling devices.

The SDS11846 specification section 3.7 should explain how this should work form a Z/IP Gateway client.

This is for polling devices. If you want to poll a status of the device or to see if the device is operational the specification says the strategy is to poll the device with a specified rate limit.

Better cross platform support

As mentioned in #85 we need to do some testing and verifying support various platforms, adding to documentation as needed. Right now Grizzly targets Nerves based embedded Linux systems, so we are a little blind to the problems with running Grizzly on different platforms.

Primarily Mac OS X and Linux Ubuntu-based systems are a good place to start.

Maybe provide build scripts for building the zipgateway binary

Maybe we should add support for mix task for people to cross-compile zipgateway for a target (defaults to their host). Ideally, they can just provide the zip file that they downloaded from SiliconLabs or maybe a directory for the source of zipgateway.

This can be done iteratively, which compiling for host-only would probably be good first past and we can add features later.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.