work on network.

pull/224/head
connor rigby 2017-01-10 11:17:12 -08:00
parent 800c80c50f
commit 8d72ed51ab
11 changed files with 406 additions and 76 deletions

View File

@ -40,7 +40,7 @@ defmodule Farmbot.Mixfile do
version: @version} ]
},
applications: applications(),
included_applications: [:gen_mqtt, :"farmbot_system_#{target(Mix.env)}"]]
included_applications: [:gen_mqtt]]
end
# common for test, prod, and dev
@ -60,6 +60,7 @@ defmodule Farmbot.Mixfile do
:quantum,
:gen_stage,
:nerves,
:"farmbot_system_#{target(Mix.env)}",
:farmbot_system,
:farmbot_auth,
:farmbot_configurator,

View File

@ -61,7 +61,7 @@ defmodule Farmbot.Configurator.Router do
spawn fn() ->
# sleep to allow the request to finish.
Process.sleep(100)
Logger.debug "THAT ISNT WORKINGF YET!!!"
Farmbot.System.Network.restart
end
conn |> send_resp(200, "OK")
end

View File

@ -30,8 +30,8 @@ defmodule Farmbot.System.Network.Ntp do
:stderr_to_stdout])
case handle_port(port) do
:ok -> :ok
{:error, _} ->
Logger.debug ">> failed to get time. trying again."
{:error, reason} ->
Logger.debug ">> failed to get time: #{inspect reason} trying again."
# kill old ntp if it exists
System.cmd("killall", ["ntpd"])
# sleep for a second

View File

@ -15,6 +15,10 @@ defmodule Farmbot.System.Network.SSH do
def start_link(), do: GenServer.start_link(__MODULE__, [], name: __MODULE__)
def stop(reason) do
GenServer.stop(__MODULE__, reason)
end
def handle_info({:EXIT, port, reason}, state)
when state == port do
Logger.error ">>`s ssh client died: #{inspect reason}"

View File

@ -0,0 +1,148 @@
defmodule Farmbot.System.Network do
@moduledoc """
Network functionality.
"""
require Logger
use GenServer
alias Farmbot.System.FS.ConfigStorage, as: CS
alias Farmbot.System.Network.SSH
alias Farmbot.System.Network.Ntp
alias Farmbot.Auth
@spec mod(atom) :: atom
defp mod(target), do: Module.concat([Farmbot, System, target, Network])
def init(target) do
Logger.debug ">> is starting networking"
m = mod(target)
{:ok, _cb} = m.start_link
{:ok, interface_config} = get_config("interfaces")
parse_and_start_config(interface_config, m)
{:ok, target}
end
# if networking is disabled.
defp parse_and_start_config(nil, _), do: :ok
defp parse_and_start_config(config, m) do
for {interface, settings} <- config do
m.start_interface(interface, settings)
end
end
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
@doc """
Scans for wireless ssids.
"""
@spec scan(String.t) :: [String.t]
def scan(interface_name) do
GenServer.call(__MODULE__, {:scan, interface_name})
end
@doc """
Restarts networking services. This will block.
"""
def restart() do
stop_all()
start_all()
end
@doc """
Starts an interface
"""
def start_interface(interface, settings) do
GenServer.call(__MODULE__, {:start, interface, settings}, :infinity)
end
@doc """
Stops an interface
"""
def stop_interface(interface) do
GenServer.call(__MODULE__, {:stop, interface}, :infinity)
end
@doc """
Stops all interfaces
"""
def stop_all do
{:ok, interfaces} = get_config("interfaces")
if interfaces do
for {iface, _} <- interfaces do
stop_interface(iface)
end
end
end
@doc """
Starts all interfaces
"""
def start_all do
{:ok, interfaces} = get_config("interfaces")
if interfaces do
for {iface, settings} <- interfaces do
start_interface(iface, settings)
end
end
end
@doc """
Connected to the World Wide Web. Should be called from the
callback module.
"""
def on_connect(fun \\ nil) do
# this happens because on wifi we try to do stuff before linux is
# finished setting stuff up.
Process.sleep(2000)
Logger.debug ">> is connected to the World Wide Web"
{:ok, ssh} = get_config("ssh")
{:ok, ntp} = get_config("ntp")
if ssh, do: SSH.start_link
if ntp, do: Ntp.set_time
if fun do
spawn fun
end
Auth.try_log_in
end
@spec get_config(String.t) :: {:ok, any}
@spec get_config() :: {:ok, false | map}
defp get_config(key), do: GenServer.call(CS, {:get, Network, key})
defp get_config, do: GenServer.call(CS, {:get, Network, :all})
# GENSERVER STUFF
def handle_call({:scan, interface_name}, _, target) do
f = mod(target).scan(interface_name)
{:reply, f, target}
end
def handle_call({:start, interface, settings}, _, target) do
f = mod(target).start_interface(interface, settings)
{:reply, f, target}
end
def handle_call({:stop, interface}, _, target) do
f = mod(target).stop_interface(interface)
{:reply, f, target}
end
def terminate(reason, target) do
ssh_pid = Process.whereis(SSH)
if ssh_pid do
SSH.stop(reason)
end
target_pid = Process.whereis(mod(target))
if target_pid do
GenServer.stop(target_pid, reason)
end
end
# Behavior
@type return_type :: :ok | {:error, term}
@callback scan(String.t) :: [String.t]
@callback start_interface(String.t, map) :: return_type
@callback stop_interface(String.t) :: return_type
@callback start_link :: {:ok, pid}
end

View File

@ -1,57 +0,0 @@
defmodule Farmbot.System.Network do
@moduledoc """
Network functionality.
"""
require Logger
use GenServer
@spec mod(atom) :: atom
defp mod(target), do: Module.concat([Farmbot, System, target, Network])
def init(target) do
Logger.debug ">> is starting networking"
m = mod(target)
{:ok, _cb} = m.start_link
{:ok, target}
end
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
@doc """
Scans for wireless ssids.
"""
@spec scan(String.t) :: [String.t]
def scan(interface_name) do
GenServer.call(__MODULE__, {:scan, interface_name})
end
@doc """
Restarts networking services. This will block.
"""
def restart() do
GenServer.call(__MODULE__, :restart_network, :infinity)
end
# GENSERVER STUFF
def handle_call({:scan, interface_name}, _, target) do
f = mod(target).scan(interface_name)
{:reply, f, target}
end
def handle_call(:restart_network, _, target) do
mod(target).restart
{:reply, :ok, target}
end
# Behavior
@type return_type :: :ok | {:error, term}
@callback scan(String.t) :: [String.t]
@callback start_interface(String.t) :: return_type
@callback stop_interface(String.t) :: return_type
@callback restart_interface(String.t) :: return_type
@callback stop_all :: return_type
@callback start_all :: return_type
@callback start_link :: {:ok, pid}
end

View File

@ -2,8 +2,16 @@ defmodule Module.concat([Farmbot, System, "development", Network]) do
@moduledoc false
@behaviour Farmbot.System.Network
use GenServer
def init(_) do
spawn fn() ->
# sleep because dev mode thinks network is up before it is technically possible.
Process.sleep(2500)
Farmbot.System.Network.on_connect()
end
{:ok, []}
end
def start_link(), do: GenServer.start_link(__MODULE__, [], name: __MODULE__)
def init(_), do: {:ok, []}
def scan(_), do: []
def start_all, do: :ok
def stop_all, do: :ok

View File

@ -2,12 +2,98 @@ defmodule Module.concat([Farmbot, System, "rpi2", Network]) do
@moduledoc false
@behaviour Farmbot.System.Network
use GenServer
require Logger
alias Nerves.InterimWiFi, as: NervesWifi
alias Farmbot.System.Network.Hostapd
def start_link(), do: GenServer.start_link(__MODULE__, [], name: __MODULE__)
def init(_), do: {:ok, []}
def scan(_), do: []
def start_all, do: :ok
def stop_all, do: :ok
def start_interface(_), do: :ok
def stop_interface(_), do: :ok
def restart_interface(_), do: :ok
def init(_) do
GenEvent.add_handler(event_manager(),
Module.concat([Farmbot, System, "rpi2", Network, EventManager]), [])
{:ok, %{}}
end
# don't start this interface
def start_interface(_interface, %{"default" => false}) do
:ok
end
def start_interface(interface, %{"default" => "dhcp", "type" => "wired"} = s) do
{:ok, pid} = NervesWifi.setup(interface, [])
GenServer.cast(__MODULE__, {:start_interface, interface, s, pid})
:ok
end
def start_interface(interface,
%{"default" => "dhcp",
"type" => "wireless",
"settings" => settings} = s)
do
ssid = settings["ssid"]
case settings["key_mgmt"] do
"NONE" ->
{:ok, pid} = NervesWifi.setup(interface, [ssid: ssid, key_mgmt: :NONE])
GenServer.cast(__MODULE__, {:start_interface, interface, s, pid})
"WPA-PSK" ->
psk = settings["psk"]
{:ok, pid} = NervesWifi.setup(interface,
[ssid: ssid, key_mgmt: :"WPA-PSK", psk: psk])
GenServer.cast(__MODULE__, {:start_interface, interface, s, pid})
end
:ok
end
def start_interface(interface,
%{"default" => "hostapd",
"settings" => %{"ipv4_address" => ip_addr}, "type" => "wireless"} = s)
do
{:ok, pid} = Hostapd.start_link([interface: interface, ip_address: ip_addr, manager: event_manager()])
GenServer.cast(__MODULE__, {:start_interface, interface, s, pid})
:ok
end
def stop_interface(interface) do
GenServer.call(__MODULE__, {:stop_interface, interface})
end
def scan(iface) do
{hc, 0} = System.cmd("iw", [iface, "scan", "ap-force"])
hc |> clean_ssid
end
defp event_manager, do: Nerves.NetworkInterface.event_manager()
defp clean_ssid(hc) do
hc
|> String.replace("\t", "")
|> String.replace("\\x00", "")
|> String.split("\n")
|> Enum.filter(fn(s) -> String.contains?(s, "SSID") end)
|> Enum.map(fn(z) -> String.replace(z, "SSID: ", "") end)
|> Enum.filter(fn(z) -> String.length(z) != 0 end)
end
# GENSERVER STUFF
def handle_call({:stop_interface, interface}, _, state) do
case state[interface] do
{settings, pid} ->
if settings["default"] == "hostapd" do
GenServer.stop(pid, :uhhh)
{:reply, :ok, Map.delete(state, interface)}
else
Logger.debug ">> cant stop: #{interface}"
{:reply, {:error, :not_implemented}, state}
end
_ -> {:reply, {:error, :not_started}, state}
end
end
def handle_cast({:start_interface, interface, settings, pid}, state) do
{:noreply, Map.put(state, interface, {settings, pid})}
end
def terminate(_,_state) do
# TODO STOP INTERFACES
:ok
end
end

View File

@ -0,0 +1,27 @@
defmodule Module.concat([Farmbot, System, "rpi2", Network, EventManager]) do
use GenEvent
require Logger
def handle_event({:udhcpc, _, :bound,
%{ipv4_address: _address, ifname: _interface}}, state)
do
Farmbot.System.Network.on_connect(fn() ->
nil
end)
{:ok, state}
end
# just print hostapd data
def handle_event({:hostapd, data}, state) do
Logger.debug ">> got some hostapd data: #{data}"
{:ok, state}
end
# def handle_event(event, state) do
# Logger.warn "got event: #{inspect event}"
# {:ok, state}
# end
# handle stray events that we don't care about
def handle_event(_, state), do: {:ok, state}
end

View File

@ -2,12 +2,98 @@ defmodule Module.concat([Farmbot, System, "rpi3", Network]) do
@moduledoc false
@behaviour Farmbot.System.Network
use GenServer
require Logger
alias Nerves.InterimWiFi, as: NervesWifi
alias Farmbot.System.Network.Hostapd
def start_link(), do: GenServer.start_link(__MODULE__, [], name: __MODULE__)
def init(_), do: {:ok, []}
def scan(_), do: []
def start_all, do: :ok
def stop_all, do: :ok
def start_interface(_), do: :ok
def stop_interface(_), do: :ok
def restart_interface(_), do: :ok
def init(_) do
GenEvent.add_handler(event_manager(),
Module.concat([Farmbot, System, "rpi3", Network, EventManager]), [])
{:ok, %{}}
end
# don't start this interface
def start_interface(_interface, %{"default" => false}) do
:ok
end
def start_interface(interface, %{"default" => "dhcp", "type" => "wired"} = s) do
{:ok, pid} = NervesWifi.setup(interface, [])
GenServer.cast(__MODULE__, {:start_interface, interface, s, pid})
:ok
end
def start_interface(interface,
%{"default" => "dhcp",
"type" => "wireless",
"settings" => settings} = s)
do
ssid = settings["ssid"]
case settings["key_mgmt"] do
"NONE" ->
{:ok, pid} = NervesWifi.setup(interface, [ssid: ssid, key_mgmt: :NONE])
GenServer.cast(__MODULE__, {:start_interface, interface, s, pid})
"WPA-PSK" ->
psk = settings["psk"]
{:ok, pid} = NervesWifi.setup(interface,
[ssid: ssid, key_mgmt: :"WPA-PSK", psk: psk])
GenServer.cast(__MODULE__, {:start_interface, interface, s, pid})
end
:ok
end
def start_interface(interface,
%{"default" => "hostapd",
"settings" => %{"ipv4_address" => ip_addr}, "type" => "wireless"} = s)
do
{:ok, pid} = Hostapd.start_link([interface: interface, ip_address: ip_addr, manager: event_manager()])
GenServer.cast(__MODULE__, {:start_interface, interface, s, pid})
:ok
end
def stop_interface(interface) do
GenServer.call(__MODULE__, {:stop_interface, interface})
end
def scan(iface) do
{hc, 0} = System.cmd("iw", [iface, "scan", "ap-force"])
hc |> clean_ssid
end
defp event_manager, do: Nerves.NetworkInterface.event_manager()
defp clean_ssid(hc) do
hc
|> String.replace("\t", "")
|> String.replace("\\x00", "")
|> String.split("\n")
|> Enum.filter(fn(s) -> String.contains?(s, "SSID") end)
|> Enum.map(fn(z) -> String.replace(z, "SSID: ", "") end)
|> Enum.filter(fn(z) -> String.length(z) != 0 end)
end
# GENSERVER STUFF
def handle_call({:stop_interface, interface}, _, state) do
case state[interface] do
{settings, pid} ->
if settings["default"] == "hostapd" do
GenServer.stop(pid, :uhhh)
{:reply, :ok, Map.delete(state, interface)}
else
Logger.debug ">> cant stop: #{interface}"
{:reply, {:error, :not_implemented}, state}
end
_ -> {:reply, {:error, :not_started}, state}
end
end
def handle_cast({:start_interface, interface, settings, pid}, state) do
{:noreply, Map.put(state, interface, {settings, pid})}
end
def terminate(_,_state) do
# TODO STOP INTERFACES
:ok
end
end

View File

@ -0,0 +1,27 @@
defmodule Module.concat([Farmbot, System, "rpi3", Network, EventManager]) do
use GenEvent
require Logger
def handle_event({:udhcpc, _, :bound,
%{ipv4_address: _address, ifname: _interface}}, state)
do
Farmbot.System.Network.on_connect(fn() ->
Farmbot.System.Network.stop_interface("wlan0")
end)
{:ok, state}
end
# just print hostapd data
def handle_event({:hostapd, data}, state) do
Logger.debug ">> got some hostapd data: #{data}"
{:ok, state}
end
# def handle_event(event, state) do
# Logger.warn "got event: #{inspect event}"
# {:ok, state}
# end
# handle stray events that we don't care about
def handle_event(_, state), do: {:ok, state}
end