diff --git a/.gitignore b/.gitignore index 2a8ce928..e182c460 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ ttb_last_config /nerves/* !/nerves/host !/nerves/target +_nerves_tmp # Secret config auth_secret_test.exs diff --git a/lib/farmbot.ex b/lib/farmbot.ex index eaf58fbd..9d09a33d 100644 --- a/lib/farmbot.ex +++ b/lib/farmbot.ex @@ -25,7 +25,6 @@ defmodule Farmbot do end def init([]) do - Logger.remove_backend :console RingLogger.attach() children = [ {Farmbot.Logger.Supervisor, []}, diff --git a/lib/farmbot/system/init/ecto.ex b/lib/farmbot/system/init/ecto.ex index a9c02275..e6dc60fe 100644 --- a/lib/farmbot/system/init/ecto.ex +++ b/lib/farmbot/system/init/ecto.ex @@ -75,6 +75,7 @@ defmodule Farmbot.System.Init.Ecto do migrated = migrator.(repo, migrations_path, :up, opts) pid && repo.stop(pid) Mix.Ecto.restart_apps_if_migrated(apps, migrated) + Logger.remove_backend Logger.Backends.Console Process.sleep(500) end end diff --git a/mix.exs b/mix.exs index eaf41f14..6ddc1071 100644 --- a/mix.exs +++ b/mix.exs @@ -142,7 +142,8 @@ defmodule Farmbot.Mixfile do {:nerves_runtime, "0.5.3"}, {:nerves_firmware, "~> 0.4.0"}, {:nerves_init_gadget, "~> 0.3.0", only: :dev}, - {:nerves_network, "~> 0.3.7-rc0", override: true}, + {:nerves_network, "~> 0.3"}, + {:nerves_wpa_supplicant, github: "nerves-project/nerves_wpa_supplicant", override: true}, {:dhcp_server, "~> 0.3.0"}, {:elixir_ale, "~> 1.0"}, {:mdns, "~> 1.0"}, diff --git a/mix.lock.rpi3 b/mix.lock.rpi3 index cd522ae2..e6e7a57b 100644 --- a/mix.lock.rpi3 +++ b/mix.lock.rpi3 @@ -38,7 +38,7 @@ "nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.3.1", "e8b1967fa0aff255230be539c68ec868d33884193a385caff957ebad7d6aa8af", [:mix], [{:nerves_runtime, "~> 0.4", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_init_gadget": {:hex, :nerves_init_gadget, "0.3.0", "4c8fdd6af9f9ad82763d7e93ab1a3f380f64d4b2804cf70acad907261b7b16be", [:mix], [{:mdns, "~> 1.0", [hex: :mdns, repo: "hexpm", optional: false]}, {:nerves_firmware_ssh, "~> 0.2", [hex: :nerves_firmware_ssh, repo: "hexpm", optional: false]}, {:nerves_network, "~> 0.3", [hex: :nerves_network, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.3", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:ring_logger, "~> 0.4", [hex: :ring_logger, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_leds": {:hex, :nerves_leds, "0.8.0", "193692767dca1a201b09113d242648493b9be0087bab83ebee99c3b0a254f5e1", [:mix], [], "hexpm"}, - "nerves_network": {:hex, :nerves_network, "0.3.7-rc0", "83d2967c6e90c9b2b740312885cb890bfd7a9e9025feee1464b768577b1fb28b", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nerves_network_interface, "~> 0.4.4", [hex: :nerves_network_interface, repo: "hexpm", optional: false]}, {:nerves_wpa_supplicant, "~> 0.3.2", [hex: :nerves_wpa_supplicant, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.7", [hex: :system_registry, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_network": {:hex, :nerves_network, "0.3.6", "c95779283ace071e9d12882d6a80e31edc8c476012adc61aba2ff6c306ef97b3", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nerves_network_interface, "~> 0.4.0", [hex: :nerves_network_interface, repo: "hexpm", optional: false]}, {:nerves_wpa_supplicant, "~> 0.3.0", [hex: :nerves_wpa_supplicant, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.4", [hex: :system_registry, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_network_interface": {:hex, :nerves_network_interface, "0.4.4", "200b1a84bc1a7fdeaf3a1e0e2d4e9b33e240b034e73f39372768d43f8690bae0", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_runtime": {:hex, :nerves_runtime, "0.5.3", "7447a3e718762f3901046f72cc824e528f8d0565a581b8ae58f4f5b6436bca7f", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.5", [hex: :system_registry, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_system_br": {:hex, :nerves_system_br, "1.0.1", "ca3b6f3c9bad0eadbeef7567a83dbbd87208e290edb2377ff9932609e3806c30", [:mix], [], "hexpm"}, @@ -47,7 +47,7 @@ "nerves_toolchain_arm_unknown_linux_gnueabihf": {:hex, :nerves_toolchain_arm_unknown_linux_gnueabihf, "1.0.0", "39da5b503b977a594c9e386ca16a50c433b333797bc30ac941fd402ce1832274", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.4", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_toolchain_ctng": {:hex, :nerves_toolchain_ctng, "1.4.0", "ec844dd286a5281223e023edb1359c8763fef79a3af9daac45397713cff1cb88", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}], "hexpm"}, "nerves_uart": {:hex, :nerves_uart, "1.1.1", "2ba6282b45513268249e78880cd84bc37c5758ee7db9c6d92f442be21fcacc35", [:mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, - "nerves_wpa_supplicant": {:hex, :nerves_wpa_supplicant, "0.3.2", "19dc7e1248336e7f542b11b2b857ceb5b088d3eb41a6ca75b7b76628dcf67aad", [:make, :mix], [{:elixir_make, "~> 0.3", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, + "nerves_wpa_supplicant": {:git, "https://github.com/nerves-project/nerves_wpa_supplicant.git", "2b3bb056a1594bff16b44d37876e8d599349dcb5", []}, "phoenix_html": {:hex, :phoenix_html, "2.10.5", "4f9df6b0fb7422a9440a73182a566cb9cbe0e3ffe8884ef9337ccf284fc1ef0a", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "plug": {:hex, :plug, "1.4.5", "7b13869283fff6b8b21b84b8735326cc012c5eef8607095dc6ee24bd0a273d8e", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, @@ -64,7 +64,7 @@ "sqlite_ecto2": {:hex, :sqlite_ecto2, "2.2.2", "7a3e5c0521e1cb6e30a4907ba4d952b97db9b2ab5d1a4806ceeb66a10b23ba65", [:mix], [{:connection, "~> 1.0.3", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 2.2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:esqlite, "~> 0.2.3", [hex: :esqlite, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: false]}, {:sqlitex, "~> 1.3.2 or ~> 1.4", [hex: :sqlitex, repo: "hexpm", optional: false]}], "hexpm"}, "sqlitex": {:hex, :sqlitex, "1.3.3", "3aac5fd702be346f71d9de6e01702c9954484cd0971aa443490bb3bde045d919", [:mix], [{:decimal, "~> 1.1", [hex: :decimal, repo: "hexpm", optional: false]}, {:esqlite, "~> 0.2.3", [hex: :esqlite, repo: "hexpm", optional: false]}], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, - "system_registry": {:hex, :system_registry, "0.7.0", "cd3aaf2c15392fa60f93869dde49f536fcf60e54f3b15db737e7d4ebcac108f4", [:mix], [], "hexpm"}, + "system_registry": {:hex, :system_registry, "0.8.0", "09240347628b001433d18279a2759ef7237ba7361239890d8c599cca9a2fbbc2", [:mix], [], "hexpm"}, "timex": {:hex, :timex, "3.2.1", "639975eac45c4c08c2dbf7fc53033c313ff1f94fad9282af03619a3826493612", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, "tzdata": {:hex, :tzdata, "0.5.16", "13424d3afc76c68ff607f2df966c0ab4f3258859bbe3c979c9ed1606135e7352", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, diff --git a/platform/target/bootstrap/configurator/captive_portal/captive_portal.ex b/platform/target/bootstrap/configurator/captive_portal/captive_portal.ex index 85a1d7bc..2862b248 100644 --- a/platform/target/bootstrap/configurator/captive_portal/captive_portal.ex +++ b/platform/target/bootstrap/configurator/captive_portal/captive_portal.ex @@ -4,13 +4,37 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do @interface Application.get_env(:farmbot, :captive_portal_interface, "wlan0") @address Application.get_env(:farmbot, :captive_portal_address, "192.168.25.1") + + @dnsmasq_conf_file "dnsmasq.conf" + @dnsmasq_pid_file "dnsmasq.pid" + def start_link() do GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init([]) do Logger.busy(3, "Starting captive portal.") - {:ok, hostapd} = Hostapd.start_link(interface: @interface, address: @address) + ensure_interface(@interface) + + Nerves.Network.teardown(@interface) + + host_ap_opts = [ + ssid: build_ssid(), + key_mgmt: :NONE, + mode: 2, + # ap_scan: 0, + # scan_ssid: 1, + ] + Nerves.Network.setup(@interface, host_ap_opts) + + ip_opts = [ + ipv4_address_method: :static, + ipv4_address: @address, ipv4_subnet_mask: "255.255.0.0", + nameservers: [@address] + ] + + Nerves.NetworkInterface.setup(@interface, ip_opts) + dhcp_opts = [ gateway: @address, netmask: "255.255.255.0", @@ -18,15 +42,32 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do domain_servers: [@address], ] {:ok, dhcp_server} = DHCPServer.start_link(@interface, dhcp_opts) - {:ok, %{hostapd: hostapd, dhcp_server: dhcp_server}} + + dnsmasq = setup_dnsmasq(@address, @interface) + + wpa_pid = wait_for_wpa() + Nerves.WpaSupplicant.request(wpa_pid, {:AP_SCAN, 2}) + {:ok, %{dhcp_server: dhcp_server, dnsmasq: dnsmasq}} + end + + defp wait_for_wpa do + name = :"Nerves.WpaSupplicant.#{@interface}" + GenServer.whereis(name) || wait_for_wpa() end def terminate(_, state) do Logger.busy 3, "Stopping captive portal GenServer." + Logger.busy 3, "Stopping DHCP GenServer." GenServer.stop(state.dhcp_server, :normal) - Logger.busy 3, "Stopping Hostapd GenServer." - GenServer.stop(state.hostapd, :normal) + + stop_dnsmasq(state) + + Nerves.Network.teardown(@interface) + end + + def handle_info({_port, {:data, _data}}, state) do + {:noreply, state} end defp dhcp_range_begin(address) do @@ -38,4 +79,77 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do [a, b, c, _] = String.split(address, ".") Enum.join([a, b, c, "10"], ".") end + + defp ensure_interface(interface) do + unless interface in Nerves.NetworkInterface.interfaces() do + Logger.debug 2, "Waiting for #{interface}: #{inspect Nerves.NetworkInterface.interfaces()}" + Process.sleep(100) + ensure_interface(interface) + end + end + + defp build_ssid do + node_str = node() |> Atom.to_string() + case node_str |> String.split("@") do + [name, "farmbot-" <> id] -> name <> "-" <> id + _ -> "Farmbot" + end + end + + defp setup_dnsmasq(ip_addr, interface) do + dnsmasq_conf = build_dnsmasq_conf(ip_addr, interface) + File.mkdir!("/tmp/dnsmasq") + :ok = File.write("/tmp/dnsmasq/#{@dnsmasq_conf_file}", dnsmasq_conf) + dnsmasq_cmd = "dnsmasq -k --dhcp-lease " <> + "/tmp/dnsmasq/#{@dnsmasq_pid_file} " <> + "--conf-dir=/tmp/dnsmasq" + dnsmasq_port = Port.open({:spawn, dnsmasq_cmd}, [:binary]) + dnsmasq_os_pid = dnsmasq_port|> Port.info() |> Keyword.get(:os_pid) + {dnsmasq_port, dnsmasq_os_pid} + end + + defp build_dnsmasq_conf(ip_addr, interface) do + """ + interface=#{interface} + address=/#/#{ip_addr} + server=/farmbot/#{ip_addr} + local=/farmbot/ + domain=farmbot + """ + end + + defp stop_dnsmasq(state) do + case state.dnsmasq do + {dnsmasq_port, dnsmasq_os_pid} -> + Logger.busy 3, "Stopping dnsmasq" + Logger.busy 3, "Killing dnsmasq PID." + :ok = kill(dnsmasq_os_pid) + Port.close(dnsmasq_port) + Logger.success 3, "Stopped dnsmasq." + :ok + _ -> + Logger.debug 3, "Dnsmasq not running." + :ok + end + rescue + e -> + Logger.error 3, "Error stopping dnsmasq: #{Exception.message(e)}" + :ok + end + + defp kill(os_pid), do: :ok = cmd("kill -9 #{os_pid}") + + defp cmd(cmd_str) do + [command | args] = String.split(cmd_str, " ") + System.cmd(command, args, into: IO.stream(:stdio, :line)) + |> print_cmd() + end + + defp print_cmd({_, 0}), do: :ok + + defp print_cmd({_, num}) do + Logger.error(2, "Encountered an error (#{num})") + :error + end + end diff --git a/platform/target/bootstrap/configurator/captive_portal/hostapd.ex b/platform/target/bootstrap/configurator/captive_portal/hostapd.ex deleted file mode 100644 index bfd5c194..00000000 --- a/platform/target/bootstrap/configurator/captive_portal/hostapd.ex +++ /dev/null @@ -1,218 +0,0 @@ -defmodule Hostapd do - @moduledoc """ - Manages an OS process of hostapd. - """ - - defmodule State do - @moduledoc false - defstruct [:hostapd, :dnsmasq, :interface, :ip_addr] - end - - use GenServer - use Farmbot.Logger - - @hostapd_conf_file "hostapd.conf" - @hostapd_pid_file "hostapd.pid" - - @dnsmasq_conf_file "dnsmasq.conf" - @dnsmasq_pid_file "dnsmasq.pid" - - defp ensure_interface(interface) do - unless interface in Nerves.NetworkInterface.interfaces() do - Logger.debug 2, "Waiting for #{interface}: #{inspect Nerves.NetworkInterface.interfaces()}" - Process.sleep(100) - ensure_interface(interface) - end - end - - @doc false - def start_link(opts, gen_server_opts \\ []) do - GenServer.start_link(__MODULE__, opts, gen_server_opts) - end - - def init(opts) do - # We want to know if something does. - Process.flag(:trap_exit, true) - interface = Keyword.fetch!(opts, :interface) - address = Keyword.fetch!(opts, :address) - Logger.busy(3, "Starting hostapd on #{interface}") - ensure_interface(interface) - - dnsmasq_path = System.find_executable("dnsmasq") - dnsmasq_settings = if dnsmasq_path do - setup_dnsmasq(address, interface) - else - nil - end - - {hostapd_port, hostapd_os_pid} = setup_hostapd(interface, address) - - state = %State{ - hostapd: {hostapd_port, hostapd_os_pid}, - dnsmasq: dnsmasq_settings, - interface: interface, - ip_addr: address - } - - {:ok, state} - end - - - defp setup_dnsmasq(ip_addr, interface) do - dnsmasq_conf = build_dnsmasq_conf(ip_addr, interface) - File.mkdir!("/tmp/dnsmasq") - :ok = File.write("/tmp/dnsmasq/#{@dnsmasq_conf_file}", dnsmasq_conf) - dnsmasq_cmd = "dnsmasq -k --dhcp-lease " <> - "/tmp/dnsmasq/#{@dnsmasq_pid_file} " <> - "--conf-dir=/tmp/dnsmasq" - dnsmasq_port = Port.open({:spawn, dnsmasq_cmd}, [:binary]) - dnsmasq_os_pid = dnsmasq_port|> Port.info() |> Keyword.get(:os_pid) - {dnsmasq_port, dnsmasq_os_pid} - end - - defp build_dnsmasq_conf(ip_addr, interface) do - """ - interface=#{interface} - address=/#/#{ip_addr} - server=/farmbot/#{ip_addr} - local=/farmbot/ - domain=farmbot - """ - end - - defp setup_hostapd(interface, ip_addr) do - # Make sure the interface is in proper condition. - :ok = hostapd_ip_settings_up(interface, ip_addr) - # build the hostapd configuration - hostapd_conf = build_hostapd_conf(interface, build_ssid()) - # build a config file - File.mkdir!("/tmp/hostapd") - File.write!("/tmp/hostapd/#{@hostapd_conf_file}", hostapd_conf) - - hostapd_cmd = - "hostapd -P /tmp/hostapd/#{@hostapd_pid_file} " - <> "/tmp/hostapd/#{@hostapd_conf_file}" - - hostapd_port = Port.open({:spawn, hostapd_cmd}, [:binary]) - hostapd_os_pid = hostapd_port |> Port.info() |> Keyword.get(:os_pid) - {hostapd_port, hostapd_os_pid} - end - - defp hostapd_ip_settings_up(interface, ip_addr) do - :ok = cmd("ip link set #{interface} up") - :ok = cmd("ip addr add #{ip_addr}/24 dev #{interface}") - :ok - end - - defp hostapd_ip_settings_down(interface, ip_addr) do - :ok = cmd("ip link set #{interface} down") - :ok = cmd("ip addr del #{ip_addr}/24 dev #{interface}") - :ok = cmd("ip link set #{interface} up") - :ok - end - - defp build_hostapd_conf(interface, ssid) do - """ - interface=#{interface} - ssid=#{ssid} - hw_mode=g - channel=6 - auth_algs=1 - wmm_enabled=0 - """ - end - - defp build_ssid do - node_str = node() |> Atom.to_string() - case node_str |> String.split("@") do - [name, "farmbot-" <> id] -> name <> "-" <> id - _ -> "Farmbot" - end - end - - def handle_info({port, {:data, data}}, state) do - {hostapd_port, _} = state.hostapd - - cond do - port == hostapd_port -> handle_hostapd(data, state) - match?({^port, _}, state.dnsmasq) -> handle_dnsmasq(data, state) - true -> {:noreply, state} - end - end - - def handle_info(_, state), do: {:noreply, state} - - defp handle_hostapd(data, state) when is_bitstring(data) do - Logger.debug(3, String.trim(data)) - {:noreply, state} - end - - defp handle_dnsmasq(data, state) when is_bitstring(data) do - Logger.debug(3, String.trim(data)) - {:noreply, state} - end - - defp stop_hostapd(state) do - case state.hostapd do - {hostapd_port, hostapd_pid} -> - Logger.busy 3, "Stopping hostapd" - Logger.busy 3, "Killing hostapd PID." - :ok = kill(hostapd_pid) - Port.close(hostapd_port) - Logger.busy 3, "Resetting ip settings." - hostapd_ip_settings_down(state.interface, state.ip_addr) - Logger.busy 3, "removing PID." - File.rm_rf!("/tmp/hostapd") - Logger.success 3, "Stopped hostapd." - :ok - _ -> - Logger.debug 3, "Hostapd not running." - :ok - end - rescue - e -> - Logger.error 3, "Error stopping hostapd: #{Exception.message(e)}" - :ok - end - - defp stop_dnsmasq(state) do - case state.dnsmasq do - {dnsmasq_port, dnsmasq_os_pid} -> - Logger.busy 3, "Stopping dnsmasq" - Logger.busy 3, "Killing dnsmasq PID." - :ok = kill(dnsmasq_os_pid) - Port.close(dnsmasq_port) - Logger.success 3, "Stopped dnsmasq." - :ok - _ -> - Logger.debug 3, "Dnsmasq not running." - :ok - end - rescue - e -> - Logger.error 3, "Error stopping dnsmasq: #{Exception.message(e)}" - :ok - end - - def terminate(_, state) do - stop_hostapd(state) - stop_dnsmasq(state) - Nerves.NetworkInterface.ifdown(state.interface) - Nerves.NetworkInterface.ifup(state.interface) - end - - defp kill(os_pid), do: :ok = cmd("kill -9 #{os_pid}") - - defp cmd(cmd_str) do - [command | args] = String.split(cmd_str, " ") - System.cmd(command, args, into: IO.stream(:stdio, :line)) - |> print_cmd() - end - - defp print_cmd({_, 0}), do: :ok - - defp print_cmd({_, num}) do - Logger.error(2, "Encountered an error (#{num})") - :error - end -end diff --git a/platform/target/bootstrap/configurator/configurator.ex b/platform/target/bootstrap/configurator/configurator.ex index 1272b39b..55b2df58 100644 --- a/platform/target/bootstrap/configurator/configurator.ex +++ b/platform/target/bootstrap/configurator/configurator.ex @@ -7,6 +7,7 @@ defmodule Farmbot.Target.Bootstrap.Configurator do @behaviour Farmbot.System.Init use Farmbot.Logger alias Farmbot.System.ConfigStorage + alias Farmbot.Target.Bootstrap.Configurator @doc """ This particular init module should block until all settings have been validated. @@ -48,11 +49,10 @@ defmodule Farmbot.Target.Bootstrap.Configurator do import Supervisor.Spec :ets.new(:session, [:named_table, :public, read_concurrency: true]) Farmbot.System.GPIO.Leds.led_status_err() - alias Farmbot.Target.Bootstrap.Configurator ConfigStorage.destroy_all_network_configs() children = [ - {Plug.Adapters.Cowboy, scheme: :http, plug: Configurator.Router, options: [port: 80, acceptors: 1]}, - worker(Configurator.CaptivePortal, []) + worker(Configurator.CaptivePortal, []), + {Plug.Adapters.Cowboy, scheme: :http, plug: Configurator.Router, options: [port: 80, acceptors: 1]} ] opts = [strategy: :one_for_one] diff --git a/platform/target/bootstrap/configurator/router.ex b/platform/target/bootstrap/configurator/router.ex index 94dd0718..b19cd9a3 100644 --- a/platform/target/bootstrap/configurator/router.ex +++ b/platform/target/bootstrap/configurator/router.ex @@ -70,7 +70,7 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do get "/config_wireless" do try do ifname = conn.params["ifname"] || raise(MissingField, field: "ifname", message: "ifname not provided", redir: "/network") - opts = [ifname: ifname, ssids: Farmbot.Target.Network.do_scan(ifname), post_action: "config_wireless_step_1"] + opts = [ifname: ifname, ssids: Farmbot.Target.Network.scan(ifname), post_action: "config_wireless_step_1"] render_page(conn, "/config_wireless_step_1", opts) rescue e in MissingField -> redir(conn, e.redir) diff --git a/platform/target/network/manager.ex b/platform/target/network/manager.ex index 22d977a0..7062b37b 100644 --- a/platform/target/network/manager.ex +++ b/platform/target/network/manager.ex @@ -15,7 +15,6 @@ defmodule Farmbot.Target.Network.Manager do end def init({interface, opts} = args) do - Elixir.Logger.remove_backend Elixir.Logger.Backends.Console Logger.busy(3, "Waiting for interface #{interface} up.") unless interface in Nerves.NetworkInterface.interfaces() do @@ -24,6 +23,8 @@ defmodule Farmbot.Target.Network.Manager do end Logger.success(3, "Interface #{interface} is up.") + Nerves.Network.teardown("wlan0") + SystemRegistry.register() {:ok, _} = Elixir.Registry.register(Nerves.NetworkInterface, interface, []) {:ok, _} = Elixir.Registry.register(Nerves.Udhcpc, interface, []) diff --git a/platform/target/network/network.ex b/platform/target/network/network.ex index 61608d01..22bbb481 100644 --- a/platform/target/network/network.ex +++ b/platform/target/network/network.ex @@ -32,8 +32,8 @@ defmodule Farmbot.Target.Network do end @doc "Scan on an interface. " - def do_scan(iface) do - Nerves.Network.scan(iface) + def scan(iface) do + do_scan(iface) |> ScanResult.decode() |> ScanResult.sort_results() |> ScanResult.decode_security() @@ -41,6 +41,38 @@ defmodule Farmbot.Target.Network do |> Enum.map(&Map.update(&1, :ssid, nil, fn(ssid) -> to_string(ssid) end)) |> Enum.reject(&String.contains?(&1.ssid, "\\x00")) |> Enum.uniq_by(fn(%{ssid: ssid}) -> ssid end) + + end + + # While scanning in AP mode, The CTRL-EVENT-SCAN-COMPLETE event never happens. + defp wait_for_scan_results(pid, timer, count, loops_without_change, acc) + defp wait_for_scan_results(_pid, _timer, _count, 10, acc), do: acc + defp wait_for_scan_results(pid, timer, count, loops_without_change, acc) do + res = Nerves.WpaSupplicant.request(pid, {:BSS, count}) + new_acc = if res, do: [res | acc], else: acc + new_count = if res, do: count + 1, else: count + new_loops_without_change = if res, do: 0, else: loops_without_change + 1 + + receive do + :complete -> acc + {Nerves.WpaSupplicant, {:"CTRL-EVENT-BSS-REMOVED", _, _}, _} -> + Process.cancel_timer(timer) + wait_for_scan_results(pid, Process.send_after(self(), :complete, 5000), 0, 0, []) + _ -> wait_for_scan_results(pid, timer, new_count, new_loops_without_change, new_acc) + after 10 -> wait_for_scan_results(pid, timer, new_count, new_loops_without_change, new_acc) + end + end + + def do_scan(iface) do + pid = :"Nerves.WpaSupplicant.#{iface}" + Elixir.Registry.register(Nerves.WpaSupplicant, iface, []) + case Nerves.WpaSupplicant.request(pid, :SCAN) do + r when r in ["FAIL-BUSY", :ok] -> + results = wait_for_scan_results(pid, Process.send_after(self(), :complete, 5000), 0, 0, []) + Elixir.Registry.unregister(Nerves.WpaSupplicant, iface) + results + resp -> raise("Unexpected scan result: #{inspect resp}") + end end @doc "Tests if we can make dns queries."