pull/363/head
Connor Rigby 2017-10-19 11:22:19 -07:00
commit a9280a87e8
26 changed files with 514 additions and 147 deletions

1
.tool-versions 100644
View File

@ -0,0 +1 @@
erlang 20.0

View File

@ -5,17 +5,12 @@ target = Mix.Project.config()[:target]
env = Mix.env()
config :logger, utc_log: true
config :elixir, ansi_enabled: true
config :iex, :colors, enabled: true
config :ssl, protocol_version: :"tlsv1.2"
# I force colors because they are important.
config :logger, :console,
colors: [enabled: true, info: :cyan],
metadata: [],
format: "$time $metadata[$level] $levelpad$message\n"
# Iex needs colors too.
config :iex, :colors, enabled: true
# This is usually in the `priv` dir of :tzdata, but our fs is read only.
config :tzdata, :data_dir, "/tmp"

View File

@ -13,7 +13,9 @@ config :farmbot, data_path: "tmp/"
# Configure your our system.
# Default implementation needs no special stuff.
config :farmbot, :init, [
Farmbot.Host.Bootstrap.Configurator
Farmbot.Host.Bootstrap.Configurator,
Farmbot.Host.TargetConfiguratorTest.Supervisor
]
# Transports.
@ -35,7 +37,7 @@ end
# SystemTasks for host mode.
config :farmbot, :behaviour,
authorization: Farmbot.Bootstrap.Authorization,
system_tasks: Farmbot.Host.SystemTasks,
firmware_handler: Farmbot.Firmware.UartHandler
system_tasks: Farmbot.Host.SystemTasks
# firmware_handler: Farmbot.Firmware.UartHandler
config :farmbot, :uart_handler, tty: "/dev/ttyACM0"

View File

@ -9,7 +9,7 @@ import_config("auth_secret_test.exs")
config :farmbot, data_path: "tmp/"
config :farmbot, :init, [
Farmbot.Host.Bootstrap.Configurator
Farmbot.Host.Bootstrap.Configurator,
]
# Transports.

View File

@ -16,11 +16,18 @@ config :farmbot, data_path: "/root"
# Configure your our init system.
config :farmbot, :init, [
# Load consolidated protocols
Farmbot.Target.Protocols,
# Autodetects if a Arduino is plugged in and configures accordingly.
Farmbot.Firmware.UartHandler.AutoDetector,
# Allows for first boot configuration.
Farmbot.Target.Bootstrap.Configurator,
# Start up Network
Farmbot.Target.Network
Farmbot.Target.Network,
# Wait for time time come up.
Farmbot.Target.Network.WaitForTime
]
# Transports.
@ -32,7 +39,7 @@ config :farmbot, :transport, [
config :farmbot, :behaviour,
authorization: Farmbot.Bootstrap.Authorization,
system_tasks: Farmbot.Target.SystemTasks,
firmware_handler: Farmbot.Firmware.UartHandler
firmware_handler: Farmbot.Firmware.StubHandler
config :nerves_firmware_ssh,
authorized_keys: [

View File

@ -25,7 +25,8 @@ defmodule Farmbot.BotState do
end
def init([]) do
{:producer_consumer, struct(__MODULE__), subscribe_to: [Farmbot.Firmware]}
{:producer_consumer, struct(__MODULE__), subscribe_to: [Farmbot.Firmware],
dispatcher: GenStage.BroadcastDispatcher}
end
def handle_events(events, _from, state) do

View File

@ -38,7 +38,7 @@ defmodule Farmbot.Firmware do
end
def init([]) do
{:producer_consumer, %State{}, subscribe_to: [@handler]}
{:producer_consumer, %State{}, subscribe_to: [@handler], dispatcher: GenStage.BroadcastDispatcher}
end
def handle_events(gcodes, _from, state) do

View File

@ -4,6 +4,11 @@ defmodule Farmbot.Firmware.StubHandler do
require Logger
@behaviour Farmbot.Firmware.Handler
@msgs [
:dance_x,
:dance_y,
:dance_z
]
def start_link do
Logger.warn("Firmware is being stubbed.")
@ -15,7 +20,8 @@ defmodule Farmbot.Firmware.StubHandler do
end
def init([]) do
{:producer, []}
@msgs |> pick_random() |> send_random(self())
{:producer, %{position: %{x: 0, y: 0, z: 0}}, dispatcher: GenStage.BroadcastDispatcher}
end
def handle_demand(_amnt, state) do
@ -25,4 +31,36 @@ defmodule Farmbot.Firmware.StubHandler do
def handle_call({:write, _string}, _from, state) do
{:reply, :ok, state}
end
def handle_info(:dance_x, %{position: %{x: x}} = state) do
state = %{state | position: %{state.position | x: math(x)}}
@msgs |> pick_random() |> send_random(self())
{:noreply, [{:report_current_position, state.position.x, state.position.y, state.position.z}], state}
end
def handle_info(:dance_y, %{position: %{y: y}} = state) do
state = %{state | position: %{state.position | y: math(y)}}
@msgs |> pick_random() |> send_random(self())
{:noreply, [{:report_current_position, state.position.x, state.position.y, state.position.z}], state}
end
def handle_info(:dance_z, %{position: %{z: z}} = state) do
state = %{state | position: %{state.position | z: math(z)}}
@msgs |> pick_random() |> send_random(self())
{:noreply, [{:report_current_position, state.position.x, state.position.y, state.position.z}], state}
end
def send_random(msg, pid) do
Process.send_after(pid, msg, random_int())
end
def pick_random(l), do: Enum.random(l)
def random_int(max \\ nil) do
:rand.uniform(max || 100)
end
def math(num) do
apply(Kernel, Enum.random([:-, :+]), [num, random_int(20)])
end
end

View File

@ -39,7 +39,7 @@ defmodule Farmbot.Firmware.UartHandler do
Process.link(nerves)
case open_tty(nerves, tty) do
:ok -> {:producer, %State{nerves: nerves, codes: []}}
:ok -> {:producer, %State{nerves: nerves, codes: []}, dispatcher: GenStage.BroadcastDispatcher}
err -> {:stop, err, :no_state}
end
end

View File

@ -1,3 +1,50 @@
defmodule Farmbot.Firmware.UartHandler.AutoDetector do
@moduledoc "Helper for autodetecting and configuring a UART fw device."
@moduledoc """
Init module for configuring a UART handler. Here's what it does:
* Enumerates Serial devices
* Scrubs devices that should be ignored.
* If there is *ONE* device:
* Configures Farmbot.Behaviour.FirmwareHandler -> UartHandler
* Configures the device to be used.
* If there are zero or more than one device:
* Configures Farmbot.Behaviour.FirmwareHandler -> StubHandler
"""
alias Nerves.UART
alias Farmbot.Firmware.{UartHandler, StubHandler}
require Logger
#TODO(Connor) - Maybe make this configurable?
@ignore_devs ["ttyAMA0", "ttyS0"]
@doc "Autodetect relevent UART Devs."
def auto_detect do
UART.enumerate() |> Map.keys() |> Kernel.--(@ignore_devs)
end
@doc false
def start_link(_, _) do
GenServer.start_link(__MODULE__, [])
end
def init([]) do
case auto_detect() do
[dev] ->
Logger.debug "detected target UART: #{dev}"
update_fw_handler UartHandler
Application.put_env(:farmbot, :uart_handler, tty: "/dev/ttyACM0")
_ ->
Logger.error "Could not detect a UART device."
update_fw_handler StubHandler
end
:ignore
end
defp update_fw_handler(fw_handler) do
old = Application.get_all_env(:farmbot)[:behaviour]
new = Keyword.put(old, :firmware_handler, fw_handler)
Application.put_env(:farmbot, :behaviour, new)
end
end

View File

@ -14,7 +14,8 @@ defmodule Farmbot.Logger do
{
:producer_consumer,
%{meta: %Log.Meta{x: -1, y: -1, z: -1}},
subscribe_to: [Farmbot.Firmware]
subscribe_to: [Farmbot.Firmware],
dispatcher: GenStage.BroadcastDispatcher
}
end

28
mix.exs
View File

@ -65,7 +65,7 @@ defmodule Farmbot.Mixfile do
end
def application do
[mod: {Farmbot, []}, extra_applications: [:logger, :eex]]
[mod: {Farmbot, []}, extra_applications: [:logger, :eex, :ssl, :inets]]
end
defp deps do
@ -102,19 +102,23 @@ defmodule Farmbot.Mixfile do
end
defp deps(target) do
[
system(target),
{:bootloader, "~> 0.1"},
{:nerves_runtime, "~> 0.4"},
{:nerves_network, "~> 0.3"},
{:nerves_firmware_ssh, "~> 0.2"},
{:dhcp_server, "~> 0.1.3"}
]
system(target) ++
[
{:bootloader, "~> 0.1"},
{:nerves_runtime, "~> 0.4"},
{:nerves_network, github: "nerves-project/nerves_network", override: true},
{:nerves_firmware_ssh, "~> 0.2"},
{:dhcp_server, "~> 0.1.3"}
]
end
defp system("rpi3"), do: {:nerves_system_farmbot_rpi3, "0.16.2-farmbot", runtime: false}
defp system("rpi0"), do: {:nerves_system_rpi0, ">= 0.0.0", runtime: false}
defp system("qemu_arm"), do: {:nerves_system_qemu_arm, ">= 0.0.0", runtime: false}
defp system("rpi3"), do: [{:nerves_system_farmbot_rpi3, "0.16.2-farmbot", runtime: false}]
defp system("rpi0"),
do: [
{:nerves_system_farmbot_rpi0, "0.17.2-farmbot", runtime: false},
{:nerves_init_gadget, "~> 0.2", only: :dev}
]
defp package do
[

53
mix.lock.rpi0 100644
View File

@ -0,0 +1,53 @@
%{"bootloader": {:hex, :bootloader, "0.1.2", "835ddcf50b796714658f342061d5d48ebc34cbd0d81cdbd5a5a8ae00705d72b1", [], [{:distillery, "~> 1.0", [hex: :distillery, repo: "hexpm", optional: false]}], "hexpm"},
"certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [], [], "hexpm"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [], [], "hexpm"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [], [], "hexpm"},
"cowboy": {:hex, :cowboy, "1.0.4", "a324a8df9f2316c833a470d918aaf73ae894278b8aa6226ce7a9bf699388f878", [], [{:cowlib, "~> 1.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [], [], "hexpm"},
"db_connection": {:hex, :db_connection, "1.1.2", "2865c2a4bae0714e2213a0ce60a1b12d76a6efba0c51fbda59c9ab8d1accc7a8", [], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"decimal": {:hex, :decimal, "1.4.1", "ad9e501edf7322f122f7fc151cce7c2a0c9ada96f2b0155b8a09a795c2029770", [], [], "hexpm"},
"dhcp_server": {:hex, :dhcp_server, "0.1.3", "9423311a7aa25bf517f5504f085a7d69e7dc98e98cc123ee77f5e8ae4fadc701", [], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nerves_network, ">= 0.0.0", [hex: :nerves_network, repo: "hexpm", optional: true]}], "hexpm"},
"distillery": {:hex, :distillery, "1.5.2", "eec18b2d37b55b0bcb670cf2bcf64228ed38ce8b046bb30a9b636a6f5a4c0080", [], [], "hexpm"},
"dns": {:hex, :dns, "1.0.1", "1d88187fdf564d937cee202949141090707fd0c9d7fcae903a6878ef24ef5d1e", [], [{:socket, "~> 0.3.12", [hex: :socket, repo: "hexpm", optional: false]}], "hexpm"},
"ecto": {:hex, :ecto, "2.2.6", "3fd1067661d6d64851a0d4db9acd9e884c00d2d1aa41cc09da687226cf894661", [], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"elixir_make": {:hex, :elixir_make, "0.4.0", "992f38fabe705bb45821a728f20914c554b276838433349d4f2341f7a687cddf", [], [], "hexpm"},
"esqlite": {:hex, :esqlite, "0.2.3", "1a8b60877fdd3d50a8a84b342db04032c0231cc27ecff4ddd0d934485d4c0cd5", [], [], "hexpm"},
"ex_json_schema": {:hex, :ex_json_schema, "0.5.5", "d8d4c3f47b86c9e634e124d518b290dda82a8b94dcc314e45af10042fc369361", [], [], "hexpm"},
"gen_mqtt": {:hex, :gen_mqtt, "0.3.1", "6ce6af7c2bcb125d5b4125c67c5ab1f29bcec2638236509bcc6abf510a6661ed", [], [{:vmq_commons, "1.0.0", [hex: :vmq_commons, repo: "hexpm", optional: false]}], "hexpm"},
"gen_stage": {:hex, :gen_stage, "0.12.2", "e0e347cbb1ceb5f4e68a526aec4d64b54ad721f0a8b30aa9d28e0ad749419cbb", [], [], "hexpm"},
"gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [], [], "hexpm"},
"hackney": {:hex, :hackney, "1.9.0", "51c506afc0a365868469dcfc79a9d0b94d896ec741cfd5bd338f49a5ec515bfe", [], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"mdns": {:hex, :mdns, "0.1.6", "b51b902b15b50e0e1522483c6a5fb073413e3d3d6ef52a44b93a541460b47d29", [], [{:dns, "~> 1.0", [hex: :dns, repo: "hexpm", optional: false]}], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [], [], "hexpm"},
"mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [], [], "hexpm"},
"nerves": {:hex, :nerves, "0.7.5", "3aa6a336b2ad6c1c9589cc2b577511b3c4c375c1ba6c533ab9f88adb8c21f0c3", [], [{:distillery, "~> 1.4", [hex: :distillery, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.2.2", "a876f4e44ccc02606b923d7097b64dc7793384d716583cfca756b7f0dff9d441", [], [{:nerves_runtime, "~> 0.4", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_init_gadget": {:hex, :nerves_init_gadget, "0.2.1", "20f36dd062fb00e2be8817ddff1b9ced9762877cfe23f6ec1d5936a37e3fc2c8", [], [{:mdns, "~> 0.1", [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]}], "hexpm"},
"nerves_network": {:git, "https://github.com/nerves-project/nerves_network.git", "266e2069d95cc314785bb74feb50fa12304ab6fd", []},
"nerves_network_interface": {:hex, :nerves_network_interface, "0.4.2", "7a3663a07803f2f9f1e37146714d24ccec1e9349268586e4ed8c41f38641d837", [], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_runtime": {:hex, :nerves_runtime, "0.4.4", "26034bc7d13dbd46aab2f429f988656621a4d91872ccf5fa748c16630bd65016", [], [{: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, "0.13.9", "8ba822e4227cdfa7a5cf8bd950b7b59c01bce553693997293def01b6929eeb98", [], [], "hexpm"},
"nerves_system_farmbot_rpi0": {:hex, :nerves_system_farmbot_rpi0, "0.17.2-farmbot", "8cffb0b019cee635b71de0a5306baa1e530aa52f443d5faf1c28862d0e5474c6", [], [{:nerves, "~> 0.7", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "~> 0.13.7", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_toolchain_armv6_rpi_linux_gnueabi, "~> 0.11.0", [hex: :nerves_toolchain_armv6_rpi_linux_gnueabi, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_toolchain_armv6_rpi_linux_gnueabi": {:hex, :nerves_toolchain_armv6_rpi_linux_gnueabi, "0.11.0", "74ee72f15baffe773e41bae1baeef3942fc8f97fa47d9d1bf9db07c17eca90bd", [], [{:nerves, "~> 0.7", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.1", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_toolchain_ctng": {:hex, :nerves_toolchain_ctng, "1.1.0", "0f03e4a3f3beef5fe271de0148b9f106c417e57f303f635c21c74b4bd6eb68ee", [], [], "hexpm"},
"nerves_uart": {:hex, :nerves_uart, "0.1.2", "4310dbb1721a5a007b8e5c416cf81754415bde6b7e2c9aa65a059886b85e637c", [], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_wpa_supplicant": {:hex, :nerves_wpa_supplicant, "0.3.2", "19dc7e1248336e7f542b11b2b857ceb5b088d3eb41a6ca75b7b76628dcf67aad", [], [{:elixir_make, "~> 0.3", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"plug": {:hex, :plug, "1.4.3", "236d77ce7bf3e3a2668dc0d32a9b6f1f9b1f05361019946aae49874904be4aed", [], [{: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", [], [], "hexpm"},
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [], [], "hexpm"},
"ranch": {:hex, :ranch, "1.4.0", "10272f95da79340fa7e8774ba7930b901713d272905d0012b06ca6d994f8826b", [], [], "hexpm"},
"rsa": {:hex, :rsa, "0.0.1", "a63069f88ce342ffdf8448b7cdef4b39ba7dee3c1510644a39385c7e63ba246f", [], [], "hexpm"},
"sbroker": {:hex, :sbroker, "1.0.0", "28ff1b5e58887c5098539f236307b36fe1d3edaa2acff9d6a3d17c2dcafebbd0", [], [], "hexpm"},
"socket": {:hex, :socket, "0.3.12", "4a6543815136503fee67eff0932da1742fad83f84c49130c854114153cc549a6", [], [], "hexpm"},
"sqlite_ecto2": {:hex, :sqlite_ecto2, "2.2.2", "7a3e5c0521e1cb6e30a4907ba4d952b97db9b2ab5d1a4806ceeb66a10b23ba65", [], [{: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", [], [{: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", [], [], "hexpm"},
"system_registry": {:hex, :system_registry, "0.6.0", "31642177e6002d3cff2ada3553ed4e9c0a6ca015797d62d7d17c0ab8696185fc", [], [], "hexpm"},
"timex": {:hex, :timex, "3.1.24", "d198ae9783ac807721cca0c5535384ebdf99da4976be8cefb9665a9262a1e9e3", [], [{:combine, "~> 0.7", [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.1.201605", "0c4184819b9d6adedcc02107b68321c45d8e853def7a32629b7961b9f2e95f33", [], [], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [], [], "hexpm"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [], [], "hexpm"},
"vmq_commons": {:git, "https://github.com/farmbot-labs/vmq_commons.git", "e780a297d5807a3537723d590036d37abce07cc5", []}}

View File

@ -14,6 +14,7 @@
"esqlite": {:hex, :esqlite, "0.2.3", "1a8b60877fdd3d50a8a84b342db04032c0231cc27ecff4ddd0d934485d4c0cd5", [], [], "hexpm"},
"ex_json_schema": {:hex, :ex_json_schema, "0.5.5", "d8d4c3f47b86c9e634e124d518b290dda82a8b94dcc314e45af10042fc369361", [], [], "hexpm"},
"gen_mqtt": {:hex, :gen_mqtt, "0.3.1", "6ce6af7c2bcb125d5b4125c67c5ab1f29bcec2638236509bcc6abf510a6661ed", [], [{:vmq_commons, "1.0.0", [hex: :vmq_commons, repo: "hexpm", optional: false]}], "hexpm"},
"gen_stage": {:hex, :gen_stage, "0.12.2", "e0e347cbb1ceb5f4e68a526aec4d64b54ad721f0a8b30aa9d28e0ad749419cbb", [], [], "hexpm"},
"gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [], [], "hexpm"},
"hackney": {:hex, :hackney, "1.9.0", "51c506afc0a365868469dcfc79a9d0b94d896ec741cfd5bd338f49a5ec515bfe", [], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},

View File

@ -0,0 +1,106 @@
defmodule Farmbot.Host.TargetConfiguratorTest do
@moduledoc "Routes web connections."
use Plug.Router
if Mix.env() == :dev do
use Plug.Debugger, otp_app: :farmbot
end
plug(Plug.Static, from: {:farmbot, "priv/static"}, at: "/")
plug(Plug.Logger, log: :debug)
plug(Plug.Parsers, parsers: [:urlencoded, :multipart])
plug(:match)
plug(:dispatch)
require Logger
alias Farmbot.System.ConfigStorage
get "/" do
last_reset_reason =
ConfigStorage.get_config_value(:string, "authorization", "last_shutdown_reason") || ""
render_page(conn, "index", last_reset_reason: last_reset_reason)
end
get "/network" do
render_page(conn, "network", [interfaces: []])
end
get "/firmware" do
render_page(conn, "firmware")
end
get "/credentials" do
email = ConfigStorage.get_config_value(:string, "authorization", "email") || ""
pass = ConfigStorage.get_config_value(:string, "authorization", "password") || ""
server = ConfigStorage.get_config_value(:string, "authorization", "server") || ""
ConfigStorage.update_config_value(:string, "authorization", "token", nil)
render_page(conn, "credentials", server: server, email: email, password: pass)
end
post "/configure_network" do
redir(conn, "/firmware")
end
get "/finish" do
Logger.info("Configuration finished.")
render_page(conn, "finish")
end
post "/configure_firmware" do
{:ok, _, conn} = read_body(conn)
case conn.body_params do
%{"firmware_hardware" => hw} when hw in ["arduino", "farmduino"] ->
ConfigStorage.update_config_value(:string, "hardware", "firmware_hardware", hw)
# TODO Flash firmware here.
# If Application.get_env(:farmbot, :uart_handler, :tty) do...
redir(conn, "/credentials")
%{"firmware_hardware" => "custom"} ->
ConfigStorage.update_config_value(:string, "hardware", "firmware_hardware", "custom")
redir(conn, "/credentials")
_ ->
send_resp(conn, 500, "Bad firmware_hardware!")
end
end
post "/configure_credentials" do
{:ok, _, conn} = read_body(conn)
case conn.body_params do
%{"email" => email, "password" => pass, "server" => server} ->
ConfigStorage.update_config_value(:string, "authorization", "email", email)
ConfigStorage.update_config_value(:string, "authorization", "password", pass)
ConfigStorage.update_config_value(:string, "authorization", "server", server)
ConfigStorage.update_config_value(:string, "authorization", "token", nil)
redir(conn, "/finish")
_ ->
send_resp(conn, 500, "invalid request.")
end
end
match(_, do: send_resp(conn, 404, "Page not found"))
defp redir(conn, loc) do
conn
|> put_resp_header("location", loc)
|> send_resp(302, loc)
end
defp render_page(conn, page, info \\ []) do
page
|> template_file()
|> EEx.eval_file(info)
|> (fn contents -> send_resp(conn, 200, contents) end).()
rescue
e -> send_resp(conn, 500, "Failed to render page: #{page} inspect: #{Exception.message(e)}")
end
defp template_file(file) do
"#{:code.priv_dir(:farmbot)}/static/templates/#{file}.html.eex"
end
end

View File

@ -0,0 +1,18 @@
defmodule Farmbot.Host.TargetConfiguratorTest.Supervisor do
use Supervisor
def start_link(_, opts) do
Supervisor.start_link(__MODULE__, [], opts)
end
def init([]) do
children = [
Plug.Adapters.Cowboy.child_spec(
:http,
Farmbot.Host.TargetConfiguratorTest,
[],
port: 4000
)]
supervise(children, [strategy: :one_for_one])
end
end

View File

@ -15,6 +15,14 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do
@hostapd_conf_file "hostapd.conf"
@hostapd_pid_file "hostapd.pid"
defp ensure_interface(interface) do
unless interface in Nerves.NetworkInterface.interfaces() do
Logger.debug "Waiting for #{interface}"
Process.sleep(100)
ensure_interface(interface)
end
end
@doc ~s"""
Example:
Iex> Hostapd.start_link ip_address: "192.168.24.1",
@ -28,7 +36,8 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do
# We want to know if something does.
Process.flag(:trap_exit, true)
interface = Keyword.fetch!(opts, :interface)
Logger.info(">> is starting hostapd on #{interface}")
Logger.info("Starting hostapd on #{interface}")
ensure_interface(interface)
{hostapd_port, hostapd_os_pid} = setup_hostapd(interface, "192.168.24.1")
@ -108,12 +117,12 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do
name <> "-" <> id
end
defp kill(os_pid), do: :ok = "kill" |> System.cmd(["15", "#{os_pid}"]) |> print_cmd
defp kill(os_pid), do: :ok = "kill" |> System.cmd(["9", "#{os_pid}"]) |> print_cmd
defp print_cmd({_, 0}), do: :ok
defp print_cmd({res, num}) do
Logger.error(">> encountered an error (#{num}): #{res}")
Logger.error("Encountered an error (#{num}): #{res}")
:error
end
@ -136,14 +145,20 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do
{:noreply, state}
end
defp handle_hostapd(_, state), do: {:noreply, state}
def terminate(_, state) do
Logger.info(">> is stopping hostapd")
{_hostapd_port, hostapd_pid} = state.hostapd
Logger.info "Stopping hostapd"
{hostapd_port, hostapd_pid} = state.hostapd
Logger.info "Killing hostapd PID."
:ok = kill(hostapd_pid)
Logger.info "Resetting ip settings."
hostapd_ip_settings_down(state.interface, state.ip_addr)
Logger.info "removing PID."
File.rm_rf!("/tmp/hostapd")
Logger.info "Done."
Port.close(hostapd_port)
:ok
rescue
_e in ArgumentError -> :ok
end
end
@ -163,8 +178,10 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do
end
def terminate(_, state) do
Logger.debug("Stopping captive portal.")
GenServer.stop(state.hostapd, :normal)
Logger.info "Stopping captive portal GenServer."
Logger.info "Stopping DHCP GenServer."
GenServer.stop(state.dhcp_server, :normal)
Logger.info "Stopping Hostapd GenServer."
GenServer.stop(state.hostapd, :normal)
end
end

View File

@ -16,7 +16,10 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
require Logger
alias Farmbot.System.ConfigStorage
get("/", do: render_page(conn, "index"))
get "/" do
last_reset_reason = ConfigStorage.get_config_value(:string, "authorization", "last_shutdown_reason") || ""
render_page(conn, "index", [last_reset_reason: last_reset_reason])
end
get "/network" do
interfaces = Nerves.NetworkInterface.interfaces()
@ -72,13 +75,17 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
get "/finish" do
conn = render_page(conn, "finish")
Supervisor.terminate_child(
spawn fn() ->
Logger.info "Configuration finished."
Process.sleep(2500)
:ok = Supervisor.terminate_child(
Farmbot.Target.Bootstrap.Configurator,
Farmbot.Target.Bootstrap.Configurator.CaptivePortal
)
)
:ok = Supervisor.stop(Farmbot.Target.Bootstrap.Configurator, :normal)
end
Supervisor.stop(Farmbot.Target.Bootstrap.Configurator, :normal)
conn
end
@ -143,6 +150,7 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
%{"firmware_hardware" => hw} when hw in ["arduino", "farmduino"] ->
ConfigStorage.update_config_value(:string, "hardware", "firmware_hardware", hw)
# TODO Flash firmware here.
# If Application.get_env(:farmbot, :uart_handler, :tty) do...
redir(conn, "/credentials")
%{"firmware_hardware" => "custom"} ->

View File

@ -0,0 +1,37 @@
defmodule Farmbot.Target.Network.Manager do
use GenServer
require Logger
alias Nerves.Network
alias Farmbot.Target.Network.Ntp
import Farmbot.Target.Network, only: [test_dns: 0]
def start_link(interface, opts) do
GenServer.start_link(__MODULE__, {interface, opts}, [name: :"#{__MODULE__}-#{interface}"])
end
def init({interface, opts}) do
Nerves.Network.Config.drop(interface)
SystemRegistry.register()
{:ok, _} = Registry.register(Nerves.NetworkInterface, interface, [])
{:ok, _} = Registry.register(Nerves.Udhcpc, interface, [])
Network.setup(interface, opts)
{:ok, %{interface: interface, ip_address: nil, connected: false}}
end
def handle_info({:system_registry, :global, registry}, state) do
ip = get_in(registry, [:state, :network_interface, state.interface, :ipv4_address])
if ip != state.ip_address do
Logger.info("ip address changed on interface: #{state.interface}: #{ip}")
:ok = Ntp.set_time()
end
connected = match?({:ok, {:hostent, 'nerves-project.org', [], :inet, 4, _}}, test_dns())
{:noreply, %{state | ip_address: ip, connected: connected || false}}
end
def handle_info(_event, state) do
# Logger.warn "unhandled network event: #{inspect event}"
{:noreply, state}
end
end

View File

@ -0,0 +1,54 @@
defmodule Farmbot.Target.Network.Ntp do
@moduledoc """
Sets time.
"""
require Logger
import Farmbot.Target.Network, only: [test_dns: 1]
@doc """
Tries to set the time from ntp.
This will try 3 times to set the time. if it fails the thrid time,
it will return an error
"""
def set_time(tries \\ 0)
def set_time(tries) when tries < 4 do
Process.sleep(1000 * tries)
case test_dns('0.pool.ntp.org') do
{:ok, {:hostent, _url, _, _, _, _}} ->
do_try_set_time(tries)
{:error, err} ->
Logger.error "Failed to set time (#{tries}): DNS Lookup: #{inspect err}"
set_time(tries + 1)
end
end
def set_time(_), do: {:error, :timeout}
defp do_try_set_time(tries) when tries < 4 do
# we try to set ntp time 3 times before giving up.
Logger.info "Trying to set time (try #{tries})"
:os.cmd('ntpd -p 0.pool.ntp.org -p 1.pool.ntp.org')
wait_for_time(tries)
end
defp wait_for_time(tries, loops \\ 0)
defp wait_for_time(tries, loops) when loops > 5 do
:os.cmd('killall ntpd')
set_time(tries)
end
defp wait_for_time(tries, loops) do
case :os.system_time(:seconds) do
t when t > 1_474_929 ->
Logger.flush()
Logger.info "Time is set."
:ok
_ ->
Process.sleep(1_000 * loops)
wait_for_time(tries, loops + 1)
end
end
end

View File

@ -0,0 +1,20 @@
defmodule Farmbot.Target.Network.WaitForTime do
require Logger
def start_link(_, _) do
:ok = wait_for_time()
Logger.warn "Time seems to be set: #{:os.system_time(:seconds)} . Moving on."
:ignore
end
defp wait_for_time do
case :os.system_time(:seconds) do
t when t > 1_474_929 ->
:ok
_ ->
Process.sleep(1000)
# Logger.warn "Waiting for time."
wait_for_time()
end
end
end

View File

@ -0,0 +1,17 @@
defmodule Farmbot.Target.Protocols do
@moduledoc false
require Logger
def start_link(_, _) do
Logger.info("Loading consolidated protocols.")
for beamfile <- Path.wildcard("/srv/erlang/lib/farmbot-*/consolidated/*.beam") do
beamfile
|> String.replace_suffix(".beam", "")
|> to_charlist()
|> :code.load_abs()
end
:ignore
end
end

View File

@ -4,6 +4,7 @@ defmodule Farmbot.Target.Network do
@behaviour Farmbot.System.Init
alias Farmbot.System.ConfigStorage
alias ConfigStorage.NetworkInterface
alias Farmbot.Target.Network.Manager, as: NetworkManager
use Supervisor
require Logger
@ -11,67 +12,20 @@ defmodule Farmbot.Target.Network do
:inet_res.gethostbyname(hostname)
end
defmodule NetworkWatcher do
use GenServer
require Logger
# TODO Expand this.
def to_network_config(config)
def start_link(name) do
GenServer.start_link(__MODULE__, name, name: :"network_interface_#{name}")
end
def init(name) do
Logger.debug("Starting NetworkWatcher - #{name}")
SystemRegistry.register()
{:ok, %{name: name, connected: false}}
end
def handle_info({:system_registry, :global, registry}, state) do
_status = get_in(registry, [:state, :network_interface, state.name])
connected =
match?(
{:ok, {:hostent, 'nerves-project.org', [], :inet, 4, _}},
Farmbot.Target.Network.test_dns()
)
if connected do
Logger.debug("Connected!")
end
{:noreply, %{state | connected: connected}}
end
def to_network_config(%NetworkInterface{ssid: ssid, psk: psk, type: "wireless"} = config) do
Logger.debug("wireless network config: ssid: #{config.ssid}, psk: #{config.psk}")
{config.name, [ssid: ssid, security: :"WPA-PSK", psk: psk]}
end
defmodule NTP do
use GenServer
require Logger
def to_network_config(%NetworkInterface{type: "wired"} = config) do
{config.name, []}
end
def start_link do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def init([]) do
Process.send_after(self(), :time, 1000)
{:ok, %{dns: false}}
end
def handle_info(:time, state) do
dns =
match?(
{:ok, {:hostent, '0.pool.ntp.org', [], :inet, 4, _}},
Farmbot.Target.Network.test_dns('0.pool.ntp.org')
)
if dns do
Logger.debug("Have dns. Setting time.")
:os.cmd('ntpd -p 0.pool.ntp.org -p 1.pool.ntp.org')
else
Logger.warn("No dns. Trying again.")
Process.send_after(self(), :time, 1000)
end
{:ok, %{state | dns: dns}}
end
def to_child_spec({interface, opts}) do
worker(NetworkManager, [interface, opts])
end
def start_link(_, opts) do
@ -79,43 +33,10 @@ defmodule Farmbot.Target.Network do
end
def init([]) do
Logger.debug("Starting up network!")
import Ecto.Query
interfaces = ConfigStorage.all(from(i in NetworkInterface))
config = ConfigStorage.all(NetworkInterface)
Logger.info("Starting Networking")
children = config |> Enum.map(&to_network_config/1) |> Enum.map(&to_child_spec/1)
children =
Enum.map(interfaces, fn interface ->
start_iface(interface)
worker(NetworkWatcher, [interface.name])
end) ++ [worker(NTP, [])]
{:ok, sup} = supervise(children, strategy: :one_for_one)
wait_for_network()
{:ok, sup}
end
defp start_iface(%{type: "wired"} = iface) do
Nerves.Network.setup(iface.name, [])
end
defp start_iface(iface) do
Nerves.Network.setup(
iface.name,
ssid: iface.ssid,
psk: iface.psk,
key_mgmt: :"#{iface.security}"
)
end
defp wait_for_network do
time = :os.system_time()
unless time > 1_507_149_578_330_507_924 do
Process.sleep(10)
if rem(time, 10) == 0, do: Logger.debug("Waiting for time: #{time}")
wait_for_network()
end
:ok
supervise(children, strategy: :one_for_one)
end
end

View File

@ -81,7 +81,7 @@ fieldset {
select {
border-radius: 0 !important;
font-size: 1.2rem !important;
background-color: #f4f4f4 !important;
background-color: white !important;
color: #434343 !important;
height: 30px !important;
width: 100% !important;
@ -98,14 +98,19 @@ label {
margin-bottom: 5px;
}
input[type=email], input[type=password] {
input[type=email],
input[type=password],
input[type=text] {
border: none;
box-shadow: 0px 0px 10px #cccccc !important;
padding: 0.5rem;
width: 100%;
font-size: 1rem;
}
input[type=email]:focus, input[type=password]:focus {
input[type=email]:focus,
input[type=password]:focus,
input[type=text]:focus {
outline: none;
}

View File

@ -1,10 +1,21 @@
<html>
<head>
<title> Your all done! </title>
<title> Configuration Complete </title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
You're all set. Farmbot will now reboot. It will reboot if any configs are
incorrect.
<h1>Configuration Complete</h1>
<div class="widget">
<div class="widget-header">
<h5>You're all set!</h5>
</div>
<div class="widget-content">
FarmBot will now reboot to apply the configuration.
<p>
If any configuration settings are incorrect, FarmBot will reset
allowing you to enter the settings again.
</p>
</div>
</div>
</body>
</html>

View File

@ -7,12 +7,15 @@
<body>
<h1>Configure your FarmBot</h1>
<%=
last_reset_reason
%>
<div class="widget">
<div class="widget-header">
<h5>Start</h5>
</div>
<div class="widget-content">
<p>Get ready to configure your farmbot!</p>
<p>Get ready to configure your FarmBot!</p>
<ui>
<li>Network</li>
<li>Firmware</li>