Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/target.exs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ product_secret = System.get_env("NH_PRODUCT_SECRET")
if product_key && product_secret do
config :nerves_hub_link,
host: "manage.nervescloud.com",
client: NameBadge.FirmwareClient,
shared_secret: [
product_key: product_key,
product_secret: product_secret
Expand Down
3 changes: 2 additions & 1 deletion lib/name_badge/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ defmodule NameBadge.Application do
# Starts a worker by calling: NameBadge.Worker.start_link(arg)
# {NameBadge.Worker, arg},
{Registry, name: NameBadge.Registry, keys: :duplicate},
NameBadge.Socket
NameBadge.Socket,
NameBadge.FirmwareProgress
] ++ target_children(@target)

# See https://hexdocs.pm/elixir/Supervisor.html
Expand Down
9 changes: 5 additions & 4 deletions lib/name_badge/battery.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ defmodule NameBadge.Battery do
min_voltage = 3.0
max_voltage = 4.2

percentage = ((v - min_voltage) / (max_voltage - min_voltage) * 100)
|> max(0)
|> min(100)
|> round()
percentage =
((v - min_voltage) / (max_voltage - min_voltage) * 100)
|> max(0)
|> min(100)
|> round()

percentage
end
Expand Down
30 changes: 30 additions & 0 deletions lib/name_badge/firmware_client.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
if Mix.target() != :host do
defmodule NameBadge.FirmwareClient do
@moduledoc """
Custom NervesHubLink.Client that pushes firmware download progress
and reboot state into FirmwareProgress. Delays reboot by 3 seconds
so the screen can render a "Rebooting..." message first.
"""

use NervesHubLink.Client

@impl NervesHubLink.Client
def handle_fwup_message({:progress, percent}) do
NameBadge.FirmwareProgress.set_state({:downloading, percent})
end

def handle_fwup_message({:ok, 0, _}) do
NameBadge.FirmwareProgress.set_state(:rebooting)
end

def handle_fwup_message(msg), do: super(msg)

# Called in a spawned process by NervesHubLink.Client.initiate_reboot/0
# Sleep gives the screen time to render "Rebooting..." before restart
@impl NervesHubLink.Client
def reboot do
Process.sleep(3_000)
Nerves.Runtime.reboot()
end
end
end
25 changes: 25 additions & 0 deletions lib/name_badge/firmware_progress.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule NameBadge.FirmwareProgress do
@moduledoc """
Tracks firmware update state and notifies subscribers via Registry.

States: `:idle`, `{:downloading, 0..100}`, `:rebooting`.
Layout reads `state/0` synchronously during render. Screen processes
subscribe to `:firmware_progress` for re-render triggers.
"""

use Agent

def start_link(_), do: Agent.start_link(fn -> :idle end, name: __MODULE__)

def state, do: Agent.get(__MODULE__, & &1)

def set_state(new_state) do
Agent.update(__MODULE__, fn _ -> new_state end)

Registry.dispatch(NameBadge.Registry, :firmware_progress, fn pids ->
for {pid, _} <- pids, do: send(pid, {:firmware_progress, new_state})
end)
end

def subscribe, do: Registry.register(NameBadge.Registry, :firmware_progress, nil)
end
58 changes: 50 additions & 8 deletions lib/name_badge/layout.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,24 @@ defmodule NameBadge.Layout do
end

def app_layout(content, opts \\ []) do
buttons = Keyword.get(opts, :button_hints, %{})
case NameBadge.FirmwareProgress.state() do
:rebooting ->
root_layout(firmware_progress_markup(), opts)

app_layout =
"""
#{icons_markup()}
#{buttons_markup(buttons)}
_ ->
buttons = Keyword.get(opts, :button_hints, %{})

#{content}
"""
app_layout =
"""
#{firmware_progress_markup()}
#{icons_markup()}
#{buttons_markup(buttons)}

root_layout(app_layout, opts)
#{content}
"""

root_layout(app_layout, opts)
end
end

defp icons_markup() do
Expand Down Expand Up @@ -67,6 +74,41 @@ defmodule NameBadge.Layout do
"""
end

defp firmware_progress_markup() do
case NameBadge.FirmwareProgress.state() do
{:downloading, percent} ->
bar_width = round(percent * 400 / 100)

"""
#place(top + left, dx: -32pt, dy: -32pt,
stack(dir: ttb, spacing: 0pt,
rect(width: #{bar_width}pt, height: 4pt, fill: black),
rect(width: 400pt, height: 0.5pt, fill: luma(200)),
)
)
"""

:rebooting ->
"""
#place(top + left, dx: -32pt, dy: -32pt,
rect(width: 400pt, height: 4pt, fill: black)
)
#place(center + horizon,
rect(fill: white, inset: 16pt, stroke: 1pt + black, radius: 4pt)[
#align(center)[
#text(size: 24pt, font: "Silkscreen", weight: 400, tracking: -2pt)[Rebooting...]
#v(4pt)
#text(size: 14pt)[Do not remove power]
]
]
)
"""

_ ->
""
end
end

defp buttons_markup(button_hints) do
hint_text = [a: "A", b: "B", ab: "AB"]

Expand Down
9 changes: 5 additions & 4 deletions lib/name_badge/mocks/battery_mock.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ defmodule NameBadge.BatteryMock do
min_voltage = 3.0
max_voltage = 4.2

percentage = ((v - min_voltage) / (max_voltage - min_voltage) * 100)
|> max(0)
|> min(100)
|> round()
percentage =
((v - min_voltage) / (max_voltage - min_voltage) * 100)
|> max(0)
|> min(100)
|> round()

percentage
end
Expand Down
55 changes: 54 additions & 1 deletion lib/name_badge/screen.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ defmodule NameBadge.Screen do
first_render?: true,
action: nil,
mount_args: nil,
last_render: %{}
last_render: %{},
fw_progress: nil
]

def start_link(args) do
Expand Down Expand Up @@ -59,6 +60,7 @@ defmodule NameBadge.Screen do
{:ok, screen} = screen.module.mount(screen.mount_args, screen)
ButtonMonitor.subscribe(:button_1)
ButtonMonitor.subscribe(:button_2)
NameBadge.FirmwareProgress.subscribe()

process_screen(screen)
end
Expand Down Expand Up @@ -102,6 +104,56 @@ defmodule NameBadge.Screen do
process_screen(screen)
end

def handle_info({:firmware_progress, {:downloading, percent}}, screen) do
last_percent =
case screen.fw_progress do
{:downloading, p} -> p
_ -> -5
end

if percent - last_percent >= 5 or percent == 100 do
fw_tick = Map.get(screen.assigns, :_fw_tick, 0) + 1

screen = %{
screen
| fw_progress: {:downloading, percent},
assigns: Map.put(screen.assigns, :_fw_tick, fw_tick)
}

process_screen(screen)
else
{:noreply, screen}
end
end

def handle_info({:firmware_progress, :rebooting}, screen) do
fw_tick = Map.get(screen.assigns, :_fw_tick, 0) + 1

screen = %{
screen
| fw_progress: :rebooting,
assigns: Map.put(screen.assigns, :_fw_tick, fw_tick)
}

process_screen(screen)
end

def handle_info({:firmware_progress, :idle}, screen) do
fw_tick = Map.get(screen.assigns, :_fw_tick, 0) + 1

screen = %{
screen
| fw_progress: nil,
assigns: Map.put(screen.assigns, :_fw_tick, fw_tick)
}

process_screen(screen)
end

def handle_info({:firmware_progress, _other}, screen) do
{:noreply, screen}
end

def handle_info(message, screen) do
{:noreply, screen} = screen.module.handle_info(message, screen)

Expand Down Expand Up @@ -149,6 +201,7 @@ defmodule NameBadge.Screen do
defp maybe_render(%__MODULE__{} = screen) do
cond do
screen.first_render? -> {:noreply, screen, {:continue, {:render, []}}}
screen.fw_progress == :rebooting -> {:noreply, screen, {:continue, {:render, []}}}
Map.equal?(screen.last_render, screen.assigns) -> {:noreply, screen}
true -> {:noreply, screen, {:continue, {:render, [refresh_type: :partial]}}}
end
Expand Down