Packages
grizzly
8.2.2
9.1.4
9.1.2
9.1.1
9.1.0
9.0.0
8.15.3
8.15.2
8.15.1
8.15.0
8.14.0
8.13.0
8.12.0
8.11.3
8.11.2
8.11.1
8.11.0
8.10.0
8.9.0
8.8.1
8.8.0
8.7.1
8.7.0
8.6.12
8.6.11
8.6.10
8.6.9
8.6.8
8.6.7
retired
8.6.6
8.6.5
8.6.4
8.6.3
8.6.2
8.6.1
8.6.0
8.5.3
8.5.2
8.5.1
8.5.0
8.4.0
8.3.0
8.2.3
8.2.2
8.2.1
8.2.0
8.1.0
8.0.1
8.0.0
7.4.3
7.4.2
7.4.1
7.4.0
7.3.0
7.2.0
7.1.4
7.1.3
7.1.2
7.1.1
7.1.0
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.8.8
6.8.7
6.8.6
6.8.5
6.8.4
6.8.3
6.8.2
6.8.1
6.8.0
6.7.1
6.7.0
6.6.1
6.6.0
6.5.1
6.5.0
6.4.0
6.3.0
6.2.0
6.1.1
6.1.0
6.0.1
6.0.0
5.4.1
5.4.0
5.3.0
5.2.8
5.2.7
5.2.6
5.2.5
5.2.4
5.2.3
5.2.2
5.2.1
5.2.0
5.1.2
5.1.1
5.1.0
5.0.2
5.0.1
5.0.0
4.0.1
4.0.0
3.0.0
2.1.0
2.0.0
1.0.1
1.0.0
0.22.7
0.22.6
0.22.5
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.1
0.21.0
0.20.2
0.20.1
0.20.0
0.19.1
0.19.0
0.18.3
0.18.2
0.18.1
0.18.0
0.17.7
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.2
0.16.1
0.16.0
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.9.0-rc.4
0.9.0-rc.3
0.9.0-rc.2
0.9.0-rc.1
0.9.0-rc.0
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.3
0.4.2
Elixir Z-Wave library
Current section
Files
Jump to
Current section
Files
lib/grizzly/firmware_updates/firmware_update_runner.ex
defmodule Grizzly.FirmwareUpdates.FirmwareUpdateRunner do
@moduledoc false
use GenServer
alias Grizzly.{Connection, FirmwareUpdates, Options, Report}
alias Grizzly.Connections.AsyncConnection
alias Grizzly.FirmwareUpdates.FirmwareUpdateRunner.{FirmwareUpdate, Image}
require Logger
@typedoc """
At any given moment there can only be 1 `FirmwareUpdateRunner` process going so
this process is the name of this module.
However, all the functions in this module can take the pid of the process or
the name to aid in the flexibility of the calling context.
"""
@type t :: pid() | __MODULE__
@default_fragment_size 1024
@doc """
A firmware update is in progress when a `FirmwareUpdateRunner` process is running
and has received at least one request for one or more image fragments.
"""
@spec in_progress?() :: boolean()
def in_progress?() do
GenServer.call(__MODULE__, :in_progress?)
catch
:exit, {:noproc, _} -> false
end
def child_spec(opts) do
# Don't restart the firmware update if there is a failure
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [opts]},
restart: :temporary
}
end
@spec start_link(Options.t(), [FirmwareUpdates.opt()]) :: GenServer.on_start()
def start_link(_grizzly_options, opts \\ []) do
# ensure that manufacturer id is in the opts - probably should do better validation
_ = Keyword.fetch!(opts, :manufacturer_id)
default_opts = [
handler: self(),
device_id: 1,
firmware_id: 0,
hardware_version: 0,
firmware_target: 0,
max_fragment_size: @default_fragment_size,
activation_may_be_delayed?: false
]
GenServer.start_link(
__MODULE__,
Keyword.merge(default_opts, opts),
name: __MODULE__
)
end
@impl true
def init(opts) do
handler = Keyword.fetch!(opts, :handler)
device_id = Keyword.fetch!(opts, :device_id)
manufacturer_id = Keyword.fetch!(opts, :manufacturer_id)
firmware_id = Keyword.fetch!(opts, :firmware_id)
hardware_version = Keyword.fetch!(opts, :hardware_version)
firmware_target = Keyword.fetch!(opts, :firmware_target)
max_fragment_size = Keyword.fetch!(opts, :max_fragment_size)
activation_may_be_delayed? = Keyword.fetch!(opts, :activation_may_be_delayed?)
{:ok, conn} = Connection.open(device_id, mode: :async, unnamed: true)
# if the connection dies, we won't be able to continue the firmware upgrade
# if we die, we don't need the connection anymore
Process.link(conn)
{:ok,
%FirmwareUpdate{
conn: conn,
handler: handler,
device_id: device_id,
manufacturer_id: manufacturer_id,
firmware_id: firmware_id,
firmware_target: firmware_target,
hardware_version: hardware_version,
max_fragment_size: max_fragment_size,
activation_may_be_delayed?: activation_may_be_delayed?
}}
end
@spec start_firmware_update(t(), FirmwareUpdates.image_path()) :: :ok
def start_firmware_update(runner, image_path) do
GenServer.call(runner, {:start_firmware_update, image_path})
end
@spec stop(t()) :: :ok
def stop(runner \\ __MODULE__) do
GenServer.stop(runner, :normal)
end
@spec firmware_image_fragment_count() :: non_neg_integer()
def firmware_image_fragment_count(runner \\ __MODULE__) do
GenServer.call(runner, :firmware_image_fragment_count)
end
@impl true
def handle_call({:start_firmware_update, image_path}, _from, firmware_update) do
{command, new_firmware_update} =
firmware_update
|> FirmwareUpdate.put_image(image_path)
|> FirmwareUpdate.next_command(:updating)
{:ok, command_ref} =
AsyncConnection.send_command(firmware_update.conn, command,
timeout: 120_000,
more_info: true
)
{:reply, :ok, FirmwareUpdate.update_command_ref(new_firmware_update, command_ref)}
end
def handle_call(
:firmware_image_fragment_count,
_from,
%FirmwareUpdate{image: image} = firmware_update
) do
count = Image.fragment_count(image)
{:reply, count, firmware_update}
end
def handle_call(:in_progress?, _from, state) do
{:reply, FirmwareUpdate.in_progress?(state), state}
end
@impl true
# Async responses to commands sent
def handle_info({:grizzly, :report, report}, firmware_update) do
case report.type do
:ack_response ->
{:noreply, firmware_update}
:timeout ->
Logger.info(
"[Grizzly] Updating firmware of device #{firmware_update.device_id} failed: timed out: timeout."
)
respond_to_handler(
format_handler_spec(firmware_update.handler),
{:error, :timeout}
)
{:stop, :normal, firmware_update}
type when type in [:queued_delay, :queued_ping] ->
respond_to_handler(
format_handler_spec(firmware_update.handler),
{:ok, :queued}
)
{:noreply, firmware_update}
:command ->
handle_report(report, firmware_update)
end
end
def handle_info({:error, :nack_response}, firmware_update) do
Logger.info(
"[Grizzly] Received :nack_response while updating firmware of device #{firmware_update.device_id}. Ignoring it."
)
{:noreply, firmware_update}
end
@impl true
def terminate(:normal, firmware_update) do
:ok = AsyncConnection.stop(firmware_update.device_id)
:ok
end
def terminate(_reason, _firmware_update) do
:ok
end
defp handle_report(%Report{type: :ack_response}, firmware_update) do
{:noreply, firmware_update}
end
defp handle_report(report, firmware_update) do
Logger.debug("[Grizzly] Handling FW update command #{inspect(report)}")
case report do
%Report{status: :complete, type: :command, command: nil} ->
Logger.warning(
"[Grizzly] FW update report of type :command has no command: #{inspect(report)}. Ignoring it."
)
{:noreply, firmware_update}
_other ->
command = report.command
new_firmware_update = FirmwareUpdate.handle_command(firmware_update, command)
respond_to_handler(format_handler_spec(firmware_update.handler), command)
if new_firmware_update.state == :complete do
{:stop, :normal, firmware_update}
else
maybe_desired_state_with_delay = FirmwareUpdate.continuation(new_firmware_update)
final_firmware_update =
handle_continuation(maybe_desired_state_with_delay, new_firmware_update)
{:noreply, final_firmware_update}
end
end
end
defp handle_continuation(nil, firmware_update) do
firmware_update
end
defp handle_continuation({desired_state, delay_msecs}, firmware_update) do
Logger.debug(
"[Grizzly] Handling FW update continuation to desired state #{inspect(desired_state)} after #{delay_msecs} msecs"
)
{command, new_firmware_update} = FirmwareUpdate.next_command(firmware_update, desired_state)
:timer.sleep(delay_msecs)
{:ok, command_ref} =
AsyncConnection.send_command(new_firmware_update.conn, command,
timeout: 120_000,
more_info: true
)
Logger.debug("[Grizzly] Sent FW update continuation #{inspect(command)}")
continued_firmware_update =
FirmwareUpdate.update_command_ref(new_firmware_update, command_ref)
maybe_desired_state_with_delay = FirmwareUpdate.continuation(continued_firmware_update)
handle_continuation(maybe_desired_state_with_delay, continued_firmware_update)
end
defp format_handler_spec({_handler_module, _handler_opts} = handler), do: handler
defp format_handler_spec(handler) when is_pid(handler), do: handler
defp format_handler_spec(handler), do: {handler, []}
defp respond_to_handler(handler, command) when is_pid(handler) do
send(handler, {:grizzly, :report, command})
end
defp respond_to_handler({handler_module, handler_opts}, command) do
# TODO - Consider using a handler runner genserver for calling the plugin inclusion handler
spawn_link(fn -> handler_module.handle_command(command, handler_opts) end)
end
end