Skip to content

Commit

Permalink
Generate zipgateway.cfg from options passed in
Browse files Browse the repository at this point in the history
When having Grizzly start zipgateway (useful for Nerves systems) we now
generate the `zipgateway.cfg` file before running the binary. This is
useful for device specific information like product id to be passed into
the configuration. Also, this will allow for optioning out of the
default scripts provided. If running in a Nerves system you can write
your own scripts and put them in the `rootfs_overly` directory and
provide that path to the script in the configuration.

The base configuration generated when there are no options provided is
the basic configuration that works in the Nerves environment.
  • Loading branch information
mattludwigs committed Nov 5, 2019
1 parent e0a05b7 commit e49bb00
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 2 deletions.
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,58 @@ this is loaded as it starts.
If you want to run tests without running the `zipgateway` binary provided by Silicon Labs you
can configure `run_zipgateway_bin` to be `false`:


```elixir
config :grizzly,
run_zipgateway_bin: false
```

#### 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:

* `:tun_script` - a path to the `.tun` script (default priv dir of Grizzly)
* `:manufacturer_id`: Id to set in the version report (default `0`)
* `:hardware_version` - Hardware version to set in the version report (default `1`)
* `:product_id` - Id to set in the version report (default `1`)
* `:product_type` - Id to set in the version report (default `1`)
* `:serial_log` - Log file for serial communication. Used for debugging. If this
option is not set the no logging is done (default none)

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:

```elixir
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 `0`if 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`

## Resources

* [Z-Wave Specification Documentation](https://www.silabs.com/products/wireless/mesh-networking/z-wave/specification)
14 changes: 12 additions & 2 deletions lib/grizzly/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ defmodule Grizzly.Application do
use Application
require Logger

alias Grizzly.ZipgatewayCfg

def start(_type, _args) do
children =
[
Expand Down Expand Up @@ -30,7 +32,8 @@ defmodule Grizzly.Application do

defp maybe_append_muontrap(children_list) do
with :ok <- get_run_zipgateway_bin(),
{:ok, serial_port} <- get_serial_port() do
{:ok, serial_port} <- get_serial_port(),
{:ok, zipgateway_cfg_path} <- build_zipgateway_cfg() do
priv_dir = :code.priv_dir(:grizzly) |> to_string()
_ = check_for_tuntap(:os.type())
zip_gateway_path = find_zip_gateway()
Expand All @@ -39,7 +42,7 @@ defmodule Grizzly.Application do
{MuonTrap.Daemon,
[
zip_gateway_path,
["-c", Path.join(priv_dir, "zipgateway.cfg"), "-s", serial_port],
["-c", zipgateway_cfg_path, "-s", serial_port],
[cd: priv_dir, log_output: :debug]
]}
] ++ children_list
Expand Down Expand Up @@ -136,4 +139,11 @@ defmodule Grizzly.Application do
opts -> Grizzly.Conn.Config.new(opts)
end
end

defp build_zipgateway_cfg() do
cfg_opts = Application.get_env(:grizzly, :zipgateway_cfg, %{})

ZipgatewayCfg.new(cfg_opts)
|> ZipgatewayCfg.write()
end
end
115 changes: 115 additions & 0 deletions lib/grizzly/zipgateway_cfg.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
defmodule Grizzly.ZipgatewayCfg do
@moduledoc false

@type t :: %__MODULE__{
unsolicited_destination_ip6: String.t(),
unsolicited_destination_port: :inet.port_number(),
ca_cert: Path.t(),
cert: Path.t(),
priv_key: Path.t(),
eeprom_file: Path.t(),
tun_script: Path.t(),
pvs_storage_file: Path.t(),
provisioning_config_file: Path.t(),
pan_ip6: String.t(),
lan_ip6: String.t(),
lan_gw6: String.t(),
psk: String.t(),
manufacturer_id: non_neg_integer() | nil,
hardware_version: non_neg_integer() | nil,
product_id: non_neg_integer() | nil,
product_type: non_neg_integer() | nil,
serial_log: String.t() | nil
}

defstruct unsolicited_destination_ip6: "fd00:aaaa::2",
unsolicited_destination_port: 41230,
ca_cert: "./Portal.ca_x509.pem",
cert: "./ZIPR.x509_1024.pem",
priv_key: "./ZIPR.key_1024.pem",
eeprom_file: "/root/zipeeprom.dat",
tun_script: "./zipgateway.tun",
pvs_storage_file: "/root/provisioning_list_store.dat",
provisioning_config_file: "/etc/zipgateway_provisioning_list.cfg",
pan_ip6: "fd00:bbbb::1",
lan_ip6: "fd00:aaaa::1",
lan_gw6: "::1",
psk: "123456789012345678901234567890AA",
serial_log: nil,
product_id: nil,
product_type: nil,
hardware_version: nil,
manufacturer_id: nil

@doc """
Make a new `ZipgatewayCfg.t()` from the supplied options
"""
@spec new(keyword) :: t()
def new(opts \\ []) do
opts =
Keyword.take(opts, [
:manufacturer_id,
:hardware_version,
:product_id,
:product_type,
:serial_log,
:tun_script
])

struct(__MODULE__, opts)
end

@doc """
Write the contents of the `ZipgatewayCfg.t()` to the file system
"""
@spec write(t()) :: {:ok, Path.t()} | {:error, File.posix()}
def write(cfg) do
contents = __MODULE__.to_string(cfg)
cfg_path = Path.join(System.tmp_dir(), "zipgateway.cfg")

case File.write(cfg_path, contents) do
:ok ->
{:ok, cfg_path}

{:error, _} = error ->
error
end
end

@doc """
Turn the `ZipgatewayCfg.t()` into a string
"""
@spec to_string(t()) :: String.t()
def to_string(cfg) do
"""
ZipUnsolicitedDestinationIp6=#{cfg.unsolicited_destination_ip6}
ZipUnsolicitedDestinationPort=#{cfg.unsolicited_destination_port}
ZipCaCert=#{cfg.ca_cert}
ZipCert=#{cfg.cert}
ZipPrivKey=#{cfg.priv_key}
Eepromfile=#{cfg.eeprom_file}
TunScript=#{cfg.tun_script}
PVSStorageFile=#{cfg.pvs_storage_file}
ProvisioningConfigFile=#{cfg.provisioning_config_file}
ZipPanIp6=#{cfg.pan_ip6}
ZipLanIp6=#{cfg.lan_ip6}
ZipLanGw6=#{cfg.lan_gw6}
ZipPSK=#{cfg.psk}
"""
|> maybe_put_config_item(cfg, :serial_log, "SerialLog")
|> maybe_put_config_item(cfg, :product_id, "ZipProductID")
|> maybe_put_config_item(cfg, :manufacturer_id, "ZipManufacturerID")
|> maybe_put_config_item(cfg, :hardware_version, "ZipHardwareVersion")
|> maybe_put_config_item(cfg, :product_type, "ZipProductType")
end

defp maybe_put_config_item(config_string, cfg, field, cfg_name) do
cfg_item = Map.get(cfg, field)

if cfg_item != nil do
config_string <> "#{cfg_name} = #{cfg_item}\n"
else
config_string
end
end
end
74 changes: 74 additions & 0 deletions test/grizzly/zipgateway_cfg_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
defmodule Grizzly.ZipgatewayCfgTest do
use ExUnit.Case, async: true

test "default config to string" do
output = """
ZipUnsolicitedDestinationIp6=fd00:aaaa::2
ZipUnsolicitedDestinationPort=41230
ZipCaCert=./Portal.ca_x509.pem
ZipCert=./ZIPR.x509_1024.pem
ZipPrivKey=./ZIPR.key_1024.pem
Eepromfile=/root/zipeeprom.dat
TunScript=./zipgateway.tun
PVSStorageFile=/root/provisioning_list_store.dat
ProvisioningConfigFile=/etc/zipgateway_provisioning_list.cfg
ZipPanIp6=fd00:bbbb::1
ZipLanIp6=fd00:aaaa::1
ZipLanGw6=::1
ZipPSK=123456789012345678901234567890AA
"""

cfg = Grizzly.ZipgatewayCfg.new()

assert output == Grizzly.ZipgatewayCfg.to_string(cfg)
end

test "when options are added as string" do
output = """
ZipUnsolicitedDestinationIp6=fd00:aaaa::2
ZipUnsolicitedDestinationPort=41230
ZipCaCert=./Portal.ca_x509.pem
ZipCert=./ZIPR.x509_1024.pem
ZipPrivKey=./ZIPR.key_1024.pem
Eepromfile=/root/zipeeprom.dat
TunScript=./zipgateway.tun
PVSStorageFile=/root/provisioning_list_store.dat
ProvisioningConfigFile=/etc/zipgateway_provisioning_list.cfg
ZipPanIp6=fd00:bbbb::1
ZipLanIp6=fd00:aaaa::1
ZipLanGw6=::1
ZipPSK=123456789012345678901234567890AA
ZipProductID = 1
"""

cfg = Grizzly.ZipgatewayCfg.new(product_id: 1)

assert output == Grizzly.ZipgatewayCfg.to_string(cfg)
end

test "write the cfg file to the system" do
cfg = Grizzly.ZipgatewayCfg.new()

expected_contents = """
ZipUnsolicitedDestinationIp6=fd00:aaaa::2
ZipUnsolicitedDestinationPort=41230
ZipCaCert=./Portal.ca_x509.pem
ZipCert=./ZIPR.x509_1024.pem
ZipPrivKey=./ZIPR.key_1024.pem
Eepromfile=/root/zipeeprom.dat
TunScript=./zipgateway.tun
PVSStorageFile=/root/provisioning_list_store.dat
ProvisioningConfigFile=/etc/zipgateway_provisioning_list.cfg
ZipPanIp6=fd00:bbbb::1
ZipLanIp6=fd00:aaaa::1
ZipLanGw6=::1
ZipPSK=123456789012345678901234567890AA
"""

assert {:ok, cfg_file_path} = Grizzly.ZipgatewayCfg.write(cfg)

assert expected_contents == File.read!(cfg_file_path)

:ok = File.rm!(cfg_file_path)
end
end

0 comments on commit e49bb00

Please sign in to comment.