farmbot discovery

pull/321/head
connor rigby 2017-06-14 08:14:28 -07:00
parent 67bcbf41fd
commit 20122cdfc6
9 changed files with 148 additions and 127 deletions

View File

@ -9,11 +9,13 @@ defmodule Farmbot.System.NervesCommon.Network do
target = Keyword.get(opts, :target)
quote do
@behaviour Farmbot.System.Network
use GenServer
use GenServer
require Logger
alias Nerves.InterimWiFi, as: NervesWifi
alias Farmbot.System.Network.Hostapd
alias Farmbot.Context
alias Nerves.InterimWiFi, as: NervesWifi
alias Nerves.{NetworkInterface, Udhcpc, WpaSupplicant, SSDPServer}
alias Farmbot.System.Network.Hostapd
alias Farmbot.Context
use Farmbot.DebugLog
def start_link(%Context{} = ctx),
do: GenServer.start_link(__MODULE__, ctx, name: __MODULE__)
@ -133,7 +135,7 @@ defmodule Farmbot.System.NervesCommon.Network do
end
end
def enumerate(%Context{} = _ctx), do: Nerves.NetworkInterface.interfaces -- ["lo"]
def enumerate(%Context{} = _ctx), do: NetworkInterface.interfaces -- ["lo"]
defp clean_ssid(hc) do
hc
@ -141,14 +143,13 @@ defmodule Farmbot.System.NervesCommon.Network do
|> 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.map(fn(z) -> String.replace(z, "SSID: ", "") end)
|> Enum.filter(fn(z) -> String.length(z) != 0 end)
end
# GENSERVER STUFF
def handle_call(:logged_in, _from, state) do
# Tear down hostapd here
{:reply, :ok, %{state | logging_in: false}}
end
@ -161,9 +162,9 @@ defmodule Farmbot.System.NervesCommon.Network do
end
{:reply, :ok, Map.delete(state, interface)}
else
:ok = Registry.unregister(Nerves.NetworkInterface, interface)
:ok = Registry.unregister(Nerves.Udhcpc, interface)
:ok = Registry.unregister(Nerves.WpaSupplicant, interface)
:ok = Registry.unregister(NetworkInterface, interface)
:ok = Registry.unregister(Udhcpc, interface)
:ok = Registry.unregister(WpaSupplicant, interface)
Logger.warn ">> cant stop: #{interface}"
{:reply, {:error, :not_implemented}, state}
end
@ -172,33 +173,50 @@ defmodule Farmbot.System.NervesCommon.Network do
end
def handle_cast({:start_interface, interface, settings, pid}, state) do
{:ok, _} = Registry.register(Nerves.NetworkInterface, interface, [])
{:ok, _} = Registry.register(Nerves.Udhcpc, interface, [])
{:ok, _} = Registry.register(Nerves.WpaSupplicant, interface, [])
{:ok, _} = Registry.register(NetworkInterface, interface, [])
{:ok, _} = Registry.register(Udhcpc, interface, [])
{:ok, _} = Registry.register(WpaSupplicant, interface, [])
{:noreply, Map.put(state, interface, {settings, pid})}
end
def handle_info({Nerves.Udhcpc, :bound, %{ifname: interface, ipv4_address: ip}}, state) do
if state.logging_in do
{:noreply, state}
else
that = self()
spawn fn() ->
def handle_info({:ssdp_timer, ip, uuid} = msg, state) do
fields = ssdp_fields(ip)
{:ok, _} = SSDPServer.publish "uuid:#{uuid}", "nerves:farmbot", fields
{:noreply, state}
end
Farmbot.System.Network.on_connect(state.context, fn() ->
def handle_info(
{Udhcpc, :bound, %{ifname: interface, ipv4_address: ip}},
%{logging_in: true} = state
) do
{:noreply, state}
end
def handle_info(
{Udhcpc, :bound, %{ifname: interface, ipv4_address: ip}},
state
) do
that = self()
spawn fn() ->
Farmbot.System.Network.on_connect(state.context,
# before callback
fn() ->
try do
{_, 0} = System.cmd("epmd", ["-daemon"])
:net_kernel.start(['farmbot@#{ip}'])
rescue
_ ->
Logger.warn "could not start epmd or net_kernel"
debug_log "could not start epmd or net_kernel"
:ok
end
Logger.info ">> is waiting for linux and network and what not."
Process.sleep(5000) # ye old race linux condidtion
uuid = Nerves.Lib.UUID.generate()
send(that, {:ssdp_timer, ip, uuid})
GenServer.call(that, :logged_in)
end,
# after callback
fn(token) ->
for {key, value} <- state do
if match?({%{"default" => "hostapd"}, _}, value) do
@ -208,12 +226,11 @@ defmodule Farmbot.System.NervesCommon.Network do
end
end)
end
{:noreply, %{state | logging_in: true}}
end
{:noreply, %{state | logging_in: true}}
end
def handle_info({Nerves.WpaSupplicant, {:error, :psk, :FAIL}, %{ifname: _iface}}, state) do
def handle_info({WpaSupplicant, {:error, :psk, :FAIL}, %{ifname: _iface}}, state) do
Farmbot.System.factory_reset("""
I could not authenticate with the access point. This could be a bad
password, or an unsupported network type.
@ -221,7 +238,10 @@ defmodule Farmbot.System.NervesCommon.Network do
{:stop, :factory_reset, state}
end
def handle_info({Nerves.WpaSupplicant, _event, %{ifname: _iface}}, %{retries: retries} = state) when retries > 5 do
def handle_info(
{WpaSupplicant, _event, %{ifname: _iface}},
%{retries: retries} = state
) when retries > 5 do
Farmbot.System.factory_reset("""
I could not find the wifi access point. Check that it was inputted correctly.
I tried #{retries} times and still found nothing. Maybe I'm not close enough to the access point?
@ -229,7 +249,10 @@ defmodule Farmbot.System.NervesCommon.Network do
{:noreply, state}
end
def handle_info({Nerves.WpaSupplicant, event, %{ifname: _iface}}, state) when is_atom(event) do
def handle_info(
{WpaSupplicant, event, %{ifname: _iface}},
state
) when is_atom(event) do
event = event |> Atom.to_string
wrong_key? = event |> String.contains?("reason=WRONG_KEY")
not_found? = event |> String.contains?("CTRL-EVENT-NETWORK-NOT-FOUND")
@ -254,6 +277,15 @@ defmodule Farmbot.System.NervesCommon.Network do
:ok
end
defp ssdp_fields(ip) do
[
location: "http://#{ip}/ssdp",
server: "Farmbot",
node: node(),
"cache-control": "max-age=1800"
]
end
end
end
end

View File

@ -0,0 +1,61 @@
defmodule Mix.Tasks.Farmbot.Discover do
@moduledoc """
Usage: mix farmbot.discover [OPTS]
## OPTS
* `--print` - print the output. (Optional)
* `--format=FORMAT` - format to print. (Optional, ignored if not printing.)
## FORMAT
* `json` - print as json.
* `human` - print in human readable form. (default)
## Examples
```
$ mix farmbot.discover --print
%{"cache-control": "max-age=1800", host: "192.168.29.103",
location: "http://192.168.29.103/ssdp", node: "farmbot@nerves-7e4b",
server: "Farmbot", service_name: "uuid:4d6ac078-0e4a-4a7a-b6cf-e951e5c959c5",
st: "nerves:farmbot"}
```
```
$ mix farmbot.discover --print --format json
[{"st":"nerves:farmbot","service_name":"uuid:4d6ac078-0e4a-4a7a-b6cf-e951e5c959c5","server":"Farmbot","node":"farmbot@nerves-7e4b","location":"http://192.168.29.103/ssdp","host":"192.168.29.103","cache-control":"max-age=1800"}]
```
"""
use Mix.Task
@shortdoc """
Discovers farmbots on the network via ssdp.
"""
def run(opts) do
devices = Nerves.SSDPClient.discover()
|> Enum.filter(fn({_, device}) -> device.st == "nerves:farmbot" end)
|> Enum.map(fn({sn, device}) -> Map.put(device, :service_name, sn) end)
switches = [print: :boolean, format: :string]
{kws, _, _} = OptionParser.parse(opts, switches: switches)
should_print? = Keyword.get(kws, :print, false)
if should_print? do
format = Keyword.get(kws, :format, "human")
do_print(devices, format)
end
devices
end
defp do_print(devices, "json") do
IO.puts Poison.encode!(devices)
end
defp do_print([device | rest], format) do
print_device(device)
do_print(rest, format)
end
defp do_print([], _), do: :ok
defp print_device(device) do
IO.inspect device
end
end

View File

@ -7,8 +7,11 @@ defmodule Mix.Tasks.Farmbot.Upload do
otp_app = Mix.Project.config[:app]
target = Mix.Project.config[:target]
{keywords, [ip_address], _} =
opts |> OptionParser.parse(switches: [signed: :boolean])
{keywords, ip_address} =
case opts |> OptionParser.parse(switches: [signed: :boolean]) do
{keywords, [ip_address], _} -> {keywords, ip_address}
{keywords, [], _} -> {keywords, try_discover()}
end
signed_bool = Keyword.get(keywords, :signed, false)
file_name = Path.join(["images", "#{Mix.env()}", "#{target}", find_file_name(otp_app, signed_bool)])
@ -51,4 +54,17 @@ defmodule Mix.Tasks.Farmbot.Upload do
defp find_file_name(otp_app, true), do: "#{otp_app}-signed.fw"
defp find_file_name(otp_app, false), do: "#{otp_app}.fw"
defp try_discover do
devices = Mix.Tasks.Farmbot.Discover.run([])
case devices do
[device] -> device.host
[_device | _more] -> do_raise("detected more than one farmbot.")
[] -> do_raise("could not detect farmbot.")
end
end
defp do_raise(msg) do
Mix.raise "#{msg} Please supply the ip address of your bot."
end
end

View File

@ -1,74 +0,0 @@
defmodule Mix.Tasks.Cs.New do
@moduledoc false
use Mix.Task
@shortdoc "Creates a new celery script command"
def run([new_cs]) do
IO.puts "Defining new Celery Script command: #{new_cs}"
module_string =
new_cs
|> Macro.camelize
|> build_module
module_test_string =
new_cs
|> Macro.camelize
|> build_test_module
new_cs_path = "lib/farmbot/celery_script/commands/#{new_cs}.ex"
new_cs_test_path = "test/farmbot/celery_script/commands/#{new_cs}_test.exs"
if File.exists?(new_cs_path) do
Mix.raise("#{new_cs} already exists!!!!")
end
:ok = File.write new_cs_path, module_string
:ok = File.write new_cs_test_path, module_test_string
end
def run(_), do: Mix.raise("Unexpected args!")
defp build_test_module(camelized_kind) do
"""
defmodule Farmbot.CeleryScript.Command.#{camelized_kind}Test do
use ExUnit.Case
alias Farmbot.CeleryScript.Ast
alias Farmbot.CeleryScript.Command
test "the truth" do
# TODO(Connor) fix the truth in #{camelized_kind}
assert true == false
end
end
"""
end
defp build_module(camelized_kind) do
"""
defmodule Farmbot.CeleryScript.Command.#{camelized_kind} do
#{build_module_doc(camelized_kind)}
alias Farmbot.CeleryScript.Ast
alias Farmbot.CeleryScript.Command
require Logger
@behaviour Command
#{build_run_doc(camelized_kind) |> String.trim}
@spec run(%{}, []) :: no_return
def run(%{}, []) do
#TODO Finish #{camelized_kind}
end
end
"""
end
defp build_run_doc(camelized_kind) do
~s(@doc ~s"""
#{camelized_kind}
args: %{},
body: []\n """)
end
defp build_module_doc(camelized_kind) do
~s(@moduledoc """
#{camelized_kind}\n """)
end
end

33
mix.exs
View File

@ -69,14 +69,15 @@ defmodule Farmbot.Mixfile do
end
def application do
[mod: {Farmbot, []},
[mod: {Farmbot, []},
applications: applications() ++ target_applications(@target) ++ env_applications(Mix.env()),
included_applications: [
:gen_mqtt,
:nerves_ssdp_client,
:ex_json_schema,
:fs,
:ex_rollbar,
:ssh
:gen_mqtt,
:ssh,
:fs,
] ++ included_apps(Mix.env)]
end
@ -90,6 +91,7 @@ defmodule Farmbot.Mixfile do
:nerves_uart,
:nerves_hal,
:nerves_runtime,
:nerves_ssdp_server,
:poison,
:rsa,
:nerves_lib,
@ -105,16 +107,13 @@ defmodule Farmbot.Mixfile do
:inets,
:redix,
:eex,
# :system_registry
]
end
defp target_applications("host"), do: []
defp target_applications(_system), do: [
:nerves_interim_wifi,
# :nerves_firmware_http,
:nerves_firmware,
:nerves_ssdp_server
]
defp env_applications(:prod), do: []
@ -127,7 +126,6 @@ defmodule Farmbot.Mixfile do
[
{:nerves, "0.5.1"},
# {:nerves_runtime, "~> 0.1.1"},
{:nerves_runtime, github: "nerves-project/nerves_runtime", override: true},
{:nerves_hal, github: "LeToteTeam/nerves_hal"},
@ -158,11 +156,11 @@ defmodule Farmbot.Mixfile do
# Log to syslog
{:ex_syslogger, "~> 1.3.3", only: :prod},
{:ex_rollbar, "0.1.2"},
# {:rollbax, "~> 0.6"},
# {:ex_rollbar, path: "../../ex_rollbar"},
# Other stuff
{:gen_stage, "0.11.0"},
{:gen_stage, "0.11.0" },
{:nerves_ssdp_server, "~> 0.2.2"},
{:nerves_ssdp_client, "~> 0.1.0"},
# Test/Dev only
{:credo, "~> 0.8", only: [:dev, :test], runtime: false},
@ -180,13 +178,10 @@ defmodule Farmbot.Mixfile do
{:cowboy, "~> 1.1"},
{:ex_webpack, "~> 0.1.1", runtime: false, warn_missing: false},
# {:farmbot_simulator, "~> 0.1.3", only: [:test, :dev]},
# {:farmbot_simulator, path: "../farmbot_simulator", only: [:test, :dev]},
{:farmbot_simulator, github: "farmbot-labs/farmbot_simulator", only: [:test, :dev]},
{:tzdata, "~> 0.1.201601", override: true},
{:fs, "~> 0.9.1"},
# {:system_registry, "~> 0.1"}
]
end
@ -232,18 +227,8 @@ defmodule Farmbot.Mixfile do
if File.exists?("nerves/nerves_system_#{sys}"),
do: [
{:"nerves_system_#{sys}", warn_missing: false, path: "nerves/nerves_system_#{sys}"},
# {:nerves_interim_wifi, "~> 0.2.0"},
# {:nerves_interim_wifi, path: "../nerves_interim_wifi"},
{:nerves_interim_wifi, github: "nerves-project/nerves_interim_wifi"},
# {:nerves_firmware_http, "~> 0.3.1"},
# {:nerves_firmware, "~> 0.3"},
# {:nerves_firmware, path: "../nerves_firmware", override: true},
{:nerves_firmware, github: "nerves-project/nerves_firmware", override: true},
# {:nerves_firmware, github: "nerves-project/nerves_firmware", tag: "0f558ad2402cbd5b36bd7a8a10bc2b53167de14e", override: true},
{:nerves_ssdp_server, "~> 0.2.1"},
],
else: Mix.raise("There is no existing system package for #{sys}")
end

View File

@ -47,7 +47,8 @@
"nerves_lib": {:git, "https://github.com/nerves-project/nerves_lib.git", "aac351cb3e621831a317f2d2a078257161efa551", []},
"nerves_network_interface": {:hex, :nerves_network_interface, "0.4.0", "a8e7662cd56fb4fe9060c891d35c43bbbff692ee6fd2d5efd538717da0cd96b8", [:make, :mix], [{:elixir_make, "~> 0.3", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_runtime": {:git, "https://github.com/nerves-project/nerves_runtime.git", "06da20aed5e391f0c885d69d2155126ecc4ca527", []},
"nerves_ssdp_server": {:hex, :nerves_ssdp_server, "0.2.1", "2d010552023fc1a724e8cb5c92479a58552976e6f805b6dbf09babd31f923b8f", [:mix], [], "hexpm"},
"nerves_ssdp_client": {:hex, :nerves_ssdp_client, "0.1.3", "b09dc7433b2536399885a5f0fcd1fb58283115b075f9485f86fa713547d404dc", [:mix], [], "hexpm"},
"nerves_ssdp_server": {:hex, :nerves_ssdp_server, "0.2.2", "30988caf00a175285a238fff83feccb1f662b9ac4fcc68805b2e9cc01dcf1ad8", [:mix], [], "hexpm"},
"nerves_system_br": {:hex, :nerves_system_br, "0.9.4", "5096a9dfec49d4663ccb94c4a4fe45885303fbf31108f7e9400369bdec94b5e7", [:mix], [], "hexpm"},
"nerves_toolchain_arm_unknown_linux_gnueabihf": {:hex, :nerves_toolchain_arm_unknown_linux_gnueabihf, "0.10.0", "18200c6cc3fcda1cbe263b7f7d50ff05db495b881ade9436cd1667b3e8e62429", [:mix], [{:nerves, "~> 0.4", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 0.9", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_toolchain_armv6_rpi_linux_gnueabi": {:hex, :nerves_toolchain_armv6_rpi_linux_gnueabi, "0.10.0", "a730667fb22710270d3e9fdab8ce7230381c424f65b0381feb693829bc460f80", [:mix], [{:nerves, "~> 0.4", [hex: :nerves, optional: false]}, {:nerves_toolchain_ctng, "~> 0.9", [hex: :nerves_toolchain_ctng, optional: false]}]},