Network updates

* Add handler for network not found.
  * if never connected - reset
  * if no delay configured - reset
  * if delay configured, start timer. reset if timer completes before
  connection.
* Update facotry reset message display.
* Allow manual input for wireless ssid.
This commit is contained in:
connor rigby 2017-11-27 12:46:08 -08:00
parent a2da261066
commit 6b3cdcb612
5 changed files with 141 additions and 50 deletions

View file

@ -43,8 +43,12 @@ defmodule Farmbot.Logger.Console do
end
defp maybe_log(%Farmbot.Log{module: module} = log) do
# should_log = List.first(Module.split(module)) == "Farmbot"
IO.inspect log
should_log = List.first(Module.split(module)) == "Farmbot"
if should_log do
IO.inspect log
else
:ok
end
end
def handle_call({:set_verbosity_level, num}, _from, state) do

View file

@ -56,6 +56,11 @@ defmodule Farmbot.System do
@system_tasks.shutdown(formatted)
end
defp write_file(nil) do
file = Path.join(@data_path, "last_shutdown_reason")
File.rm_rf(file)
end
defp write_file(reason) do
file = Path.join(@data_path, "last_shutdown_reason")
File.write!(file, reason)
@ -71,18 +76,27 @@ defmodule Farmbot.System do
rescue
_ ->
[_ | [_ | stack]] = System.stacktrace()
stack = Enum.map(stack, fn er -> "\t#{inspect(er)}" end) |> Enum.join("\r\n")
do_format_reason(reason) <> """
environment: #{@env}
source_ref: #{@ref}
target: #{@target}
stack = Enum.map(stack, fn er -> "\t#{inspect(er)}" end) |> Enum.join(",\r\n <p>")
formated = do_format_reason(reason)
footer = """
<hr>
<p>
<p>
<p> <strong> environment: </strong> #{@env}
<p> <strong> source_ref: </strong> #{@ref}
<p> <strong> target: </strong> #{@target}
<p>
<p>
Stacktrace:
[
#{stack}
]
<p> [#{stack}]
<hr>
"""
if formated do
formated <> footer
else
nil
end
end
# This mess of pattern matches cleans up erlang startup errors. It's very
@ -98,14 +112,17 @@ defmodule Farmbot.System do
defp do_format_reason({:error, {:shutdown, {:failed_to_start_child, child, rest}}}) do
{failed_child, failed_reason} = enumerate_ftsc_error(child, rest)
if failed_reason do
"""
Failed to start child: #{failed_child}
reason: #{do_format_reason(failed_reason)}
"""
Failed to start child: #{failed_child}
reason: #{do_format_reason(failed_reason)}
This is likely a bug. Please copy or screenshot this error and send it to
the Farmbot developers.
"""
This is likely a bug. Please copy or screenshot this error and send it to
the Farmbot developers.
"""
else
nil
end
end
defp do_format_reason({:bad_return, {Farmbot.Bootstrap.Supervisor, :init, error}}) do
@ -130,6 +147,13 @@ defmodule Farmbot.System do
"""
end
defp do_format_reason({:badarg, [{:ets, :lookup_element, _, _} | _]}) do
"""
Bad Ecto call. This usually is a result of an over the air update and can
likely be ignored.
"""
end
defp do_format_reason(reason), do: do_format_reason({:error, reason})
# This cleans up nested supervisors/workers.

View file

@ -1,6 +1,7 @@
defmodule Farmbot.Target.Network.Manager do
use GenServer
use Farmbot.Logger
alias Farmbot.System.ConfigStorage
alias Nerves.Network
alias Farmbot.Target.Network.Ntp
import Farmbot.Target.Network, only: [test_dns: 0]
@ -10,6 +11,7 @@ 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 up.")
unless interface in Nerves.NetworkInterface.interfaces() do
Process.sleep(1000)
@ -20,7 +22,7 @@ defmodule Farmbot.Target.Network.Manager do
{:ok, _} = Registry.register(Nerves.Udhcpc, interface, [])
{:ok, _} = Registry.register(Nerves.WpaSupplicant, interface, [])
Network.setup(interface, opts)
{:ok, %{interface: interface, ip_address: nil, connected: false}}
{:ok, %{interface: interface, ip_address: nil, connected: false, not_found_timer: nil}}
end
def handle_info({:system_registry, :global, registry}, state) do
@ -32,7 +34,14 @@ defmodule Farmbot.Target.Network.Manager do
end
connected = match?({:ok, {:hostent, 'nerves-project.org', [], :inet, 4, _}}, test_dns())
{:noreply, %{state | ip_address: ip, connected: connected || false}}
if connected do
if state.not_found_timer do
Process.cancel_timer(state.not_found_timer)
end
{:noreply, %{state | ip_address: ip, connected: true, not_found_timer: nil}}
else
{:noreply, %{state | connected: false}}
end
end
def handle_info({Nerves.WpaSupplicant, {:INFO, "WPA: 4-Way Handshake failed - pre-shared key may be incorrect"}, _}, state) do
@ -41,6 +50,40 @@ defmodule Farmbot.Target.Network.Manager do
{:stop, :normal, state}
end
def handle_info({Nerves.WpaSupplicant, :"CTRL-EVENT-NETWORK-NOT-FOUND", _}, %{not_found_timer: nil} = state) do
first_boot = ConfigStorage.get_config_value(:bool, "settings", "first_boot")
delay_timer = ConfigStorage.get_config_value(:float, "settings", "network_not_found_timer")
cond do
first_boot ->
Logger.error 1, "Network not found"
Farmbot.System.factory_reset("WIFI Authentication failed. (network not found)")
{:stop, :normal, state}
delay_timer > 0 ->
Logger.warn 1, "Network not found. Starting timer."
timer = Process.send_after(self(), :network_not_found_timer, round(delay_timer))
{:noreply, %{state | not_found_timer: timer}}
delay_timer == -1 -> {:noreply, state}
is_nil(delay_timer) ->
Logger.error 1, "Network not found"
Farmbot.System.factory_reset("WIFI Authentication failed. (network not found)")
{:stop, :normal, state}
end
end
def handle_info({Nerves.WpaSupplicant, :"CTRL-EVENT-NETWORK-NOT-FOUND"}, state) do
{:noreply, state}
end
def handle_info(:network_not_found_timer, state) do
if state.connected do
{:noreply, %{state | not_found_timer: nil}}
else
Logger.error 1, "Network not found"
Farmbot.System.factory_reset("WIFI Authentication failed. (network not found after timer)")
{:stop, :normal, state}
end
end
def handle_info(_event, state) do
# Logger.warn 3, "unhandled network event: #{inspect event}"
{:noreply, state}

View file

@ -138,6 +138,7 @@ defmodule Farmbot.System.ConfigStorage.Migrations.SeedGroups do
create_value(BoolValue, false) |> create_config(group_id, "auto_sync")
create_value(StringValue, nil) |> create_config(group_id, "firmware_hardware")
create_value(StringValue, nil) |> create_config(group_id, "timezone")
create_value(FloatValue, nil) |> create_config(group_id, "network_not_found_timer")
fpf_url = Application.get_env(:farmbot, :farmware)[:first_part_farmware_manifest_url]
create_value(StringValue, fpf_url) |> create_config(group_id, "first_party_farmware_url")
end

View file

@ -3,8 +3,29 @@
<head>
<title> Configure Farmbot's Network </title>
<link rel="stylesheet" href="/styles.css">
<script type="text/javascript">
function ssidSelectOnChange(iface) {
var elem = document.getElementById(iface + "_ssid_select");
var selected = elem.options[elem.selectedIndex];
expected_id = iface + "_ssid_manual_input";
if(selected.id == expected_id) {
replaceSelect(iface);
}
}
function replaceSelect(iface) {
console.log("Replaceing select element with input element")
var elem = document.getElementById(iface + "_ssid_select");
var inputNode = document.createElement("input");
inputNode.setAttribute("name", iface + "_ssid");
inputNode.setAttribute("type", "text");
elem.parentNode.insertBefore(inputNode, elem.nextSibling);
elem.outerHTML = "";
delete elem;
}
</script>
</head>
<body>
<h1>Configure your FarmBot</h1>
<div class="widget">
@ -13,34 +34,32 @@
</div>
<div class="widget-content">
<form action="configure_network" method="post">
<%= for {interface, settings} <- interfaces do
case settings do
%{type: :wired} ->
"""
<fieldset>
<label>Enable Ethernet ( #{interface} )</label>
<input type=checkbox name=#{interface}_enable>
<input hidden=true name=#{interface}_type value=wired>
</fieldset>
"""
%{type: :wireless, ssids: ssids} ->
"""
<fieldset>
<label>Enable Wireless ( #{interface} )</label>
<input type=checkbox name=#{interface}_enable>
<label>Network Name/SSID</label>
<select name=#{interface}_ssid>
#{Enum.reduce(ssids, "", fn(ssid, acc) ->
acc <> "<option value=#{ssid}> #{ssid} </option>"
end)}
</select>
<label>Password</label>
<input type=password name=#{interface}_psk>
<input hidden=true name=#{interface}_type value=wireless>
</fieldset>
"""
end
end %>
<%= for {interface, settings} <- interfaces do %>
<%= case settings do %>
<% %{type: :wired} -> %>
<fieldset>
<label>Enable Ethernet (<%= interface %>)</label>
<input type=checkbox name="<%= interface %>_enable">
<input hidden=true name="<%= interface %>_type" value=wired>
</fieldset>
<% %{type: :wireless, ssids: ssids} -> %>
<fieldset>
<label>Enable Wireless (<%= interface %>) </label>
<input type=checkbox name="<%= interface %>_enable">
<label>Network Name/SSID</label>
<select name="<%= interface %>_ssid" id="<%= interface %>_ssid_select" onchange="ssidSelectOnChange('<%= interface %>');">
<%= Enum.reduce(ssids, "", fn(ssid, acc) ->
acc <> "<option value=#{ssid}> #{ssid} </option>"
end) %>
<option id="<%= interface %>_ssid_manual_input"> <bold> Manual Input </bold> </option>
</select>
<label>Password</label>
<input type=password name="<%= interface %>_psk">
<input hidden=true name="<%= interface %>_type" value=wireless>
</fieldset>
<% end %>
<% end %>
</div>
</div>
<div class="button">
@ -48,6 +67,6 @@
</div>
</form>
</boty>
</body>
</html>