Packages
nerves
1.13.1
1.15.0
1.14.3
1.14.2
1.14.1
1.14.0
1.13.2
1.13.1
1.13.0
1.12.0
1.11.3
1.11.2
1.11.1
1.11.0
1.10.5
1.10.4
1.10.3
1.10.2
1.10.1
1.10.0
1.9.3
1.9.2
retired
1.9.1
1.9.0
1.8.0
1.7.17
1.7.16
1.7.15
1.7.14
retired
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
retired
1.7.1
retired
1.7.0
retired
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.5
1.5.4
1.5.3
1.5.2
1.5.1
1.5.0
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.1
1.2.0
1.1.1
1.1.0
1.0.1
1.0.0
1.0.0-rc.2
1.0.0-rc.1
1.0.0-rc.0
0.11.0
0.10.1
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.4.0-rc.0
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
Craft and deploy bulletproof embedded software
Current section
Files
Jump to
Current section
Files
lib/mix/tasks/nerves.discover.ex
# SPDX-FileCopyrightText: 2026 Frank Hunleth
#
# SPDX-License-Identifier: Apache-2.0
#
defmodule Mix.Tasks.Nerves.Discover do
@shortdoc "Discover Nerves devices on the local network"
@moduledoc """
Discover Nerves devices on the local network using mDNS
This task scans the local network for Nerves devices and displays
their information in a table format.
Linux users: Install `avahi-utils` for faster responses.
## Examples
$ mix nerves.discover
$ mix nerves.discover --timeout 10000
## Options
* `--timeout` - timeout in milliseconds to wait for replies (default 5000)
"""
use Mix.Task
alias Nerves.Fwup.Word34567
@switches [timeout: :integer]
@impl Mix.Task
def run(argv) do
{opts, _, _} = OptionParser.parse(argv, switches: @switches)
timeout = Keyword.get(opts, :timeout, 5000)
Mix.shell().info("\nDiscovering Nerves devices (waiting up to #{timeout}ms)...")
devices = NervesDiscovery.discover(timeout: timeout)
Mix.shell().info("")
if Enum.empty?(devices) do
Mix.shell().info("No devices found.")
else
Tablet.puts(devices, tablet_options(devices))
end
end
defp tablet_options(devices) do
base_columns = [:name, :addresses]
optional_columns = [
:serial,
:product,
:version,
:platform,
:uuid
]
all_keys =
Enum.reduce(devices, MapSet.new(), fn device, set ->
set_put_many(set, non_empty_keys(device))
end)
# See which of the optional columns are actually present
additional_columns =
Enum.filter(optional_columns, &MapSet.member?(all_keys, &1))
[
keys: base_columns ++ additional_columns,
formatter: &formatter/2,
column_widths: %{uuid: :expand}
]
end
defp non_empty_keys(map) do
Enum.reduce(map, [], fn {k, v}, acc -> if v != nil and v != "", do: [k | acc], else: acc end)
end
defp set_put_many(set, values), do: Enum.reduce(values, set, &MapSet.put(&2, &1))
defp formatter(:__header__, key), do: {:ok, key |> to_string() |> String.upcase()}
defp formatter(:addresses, addresses),
do: {:ok, addresses |> Enum.map(&:inet.ntoa/1) |> Enum.map_join("\n", &to_string/1)}
defp formatter(:uuid, uuid) when byte_size(uuid) == 36,
do: {:ok, "#{uuid_to_nickname(uuid)} (#{uuid})"}
defp formatter(_, _), do: :default
# Convert a UUID to a nickname using the fwup algorithm
defp uuid_to_nickname(uuid) do
# Remove hyphens from UUID and convert to binary
uuid_binary =
uuid
|> String.replace("-", "")
|> Base.decode16!(case: :mixed)
# Get the first two bytes (most significant)
<<first::8, second::8, _rest::binary>> = uuid_binary
"#{Word34567.word(first)}-#{Word34567.word(second)}"
end
end