use Elixir 1.6.0 code formatter
parent
3999ef58d9
commit
fe32cd1540
Binary file not shown.
Binary file not shown.
|
@ -1,8 +1,8 @@
|
|||
use Mix.Config
|
||||
|
||||
# Mix configs.
|
||||
target = Mix.Project.config[:target]
|
||||
env = Mix.env()
|
||||
target = Mix.Project.config()[:target]
|
||||
env = Mix.env()
|
||||
|
||||
config :logger, utc_log: true
|
||||
|
||||
|
@ -34,12 +34,11 @@ config :farmbot, :init, []
|
|||
config :farmbot, :transport, []
|
||||
|
||||
# Configure Farmbot Behaviours.
|
||||
config :farmbot, :behaviour, [
|
||||
config :farmbot, :behaviour,
|
||||
authorization: Farmbot.Bootstrap.Authorization,
|
||||
firmware_handler: Farmbot.Firmware.StubHandler,
|
||||
]
|
||||
firmware_handler: Farmbot.Firmware.StubHandler
|
||||
|
||||
repos = [Farmbot.Repo.A, Farmbot.Repo.B, Farmbot.System.ConfigStorage]
|
||||
repos = [Farmbot.Repo.A, Farmbot.Repo.B, Farmbot.System.ConfigStorage]
|
||||
config :farmbot, ecto_repos: repos
|
||||
|
||||
for repo <- [Farmbot.Repo.A, Farmbot.Repo.B] do
|
||||
|
@ -53,7 +52,9 @@ config :farmbot, Farmbot.System.ConfigStorage,
|
|||
database: "config-#{env}.sqlite3"
|
||||
|
||||
case target do
|
||||
"host" -> import_config("host/#{env}.exs")
|
||||
"host" ->
|
||||
import_config("host/#{env}.exs")
|
||||
|
||||
_ ->
|
||||
if File.exists?("config/#{target}/#{env}.exs") do
|
||||
import_config("#{target}/#{env}.exs")
|
||||
|
@ -61,9 +62,9 @@ case target do
|
|||
import_config("target/#{env}.exs")
|
||||
end
|
||||
|
||||
rootfs_overlay_dir = "config/target/rootfs_overlay_#{Mix.Project.config[:target]}"
|
||||
rootfs_overlay_dir = "config/target/rootfs_overlay_#{Mix.Project.config()[:target]}"
|
||||
|
||||
if File.exists?(rootfs_overlay_dir) do
|
||||
config :nerves, :firmware,
|
||||
rootfs_overlay: rootfs_overlay_dir
|
||||
config :nerves, :firmware, rootfs_overlay: rootfs_overlay_dir
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,8 +3,7 @@ use Mix.Config
|
|||
# You should copy this file to config/host/auth_secret.exs
|
||||
# And make sure to configure the credentials to something that makes sense.
|
||||
|
||||
config :farmbot, :authorization, [
|
||||
config :farmbot, :authorization,
|
||||
email: "admin@admin.com",
|
||||
password: "password123",
|
||||
server: "http://localhost:3000"
|
||||
]
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use Mix.Config
|
||||
|
||||
unless File.exists?("config/host/auth_secret.exs") do
|
||||
Mix.raise("You need to configure your dev environment. See `config/host/auth_secret.exs` for an example.\r\n")
|
||||
Mix.raise(
|
||||
"You need to configure your dev environment. See `config/host/auth_secret.exs` for an example.\r\n"
|
||||
)
|
||||
end
|
||||
|
||||
import_config("auth_secret.exs")
|
||||
|
@ -18,17 +21,13 @@ config :farmbot, :transport, [
|
|||
Farmbot.BotState.Transport.GenMQTT
|
||||
]
|
||||
|
||||
|
||||
# Configure Farmbot Behaviours.
|
||||
config :farmbot, :behaviour, [
|
||||
# Default Authorization behaviour.
|
||||
# Default Authorization behaviour.
|
||||
# SystemTasks for host mode.
|
||||
config :farmbot, :behaviour,
|
||||
authorization: Farmbot.Bootstrap.Authorization,
|
||||
# SystemTasks for host mode.
|
||||
system_tasks: Farmbot.Host.SystemTasks,
|
||||
system_tasks: Farmbot.Host.SystemTasks
|
||||
|
||||
# firmware_handler: Farmbot.Firmware.UartHandler
|
||||
]
|
||||
# firmware_handler: Farmbot.Firmware.UartHandler
|
||||
|
||||
config :farmbot, :uart_handler, [
|
||||
tty: "/dev/ttyACM0"
|
||||
]
|
||||
config :farmbot, :uart_handler, tty: "/dev/ttyACM0"
|
||||
|
|
|
@ -3,6 +3,7 @@ use Mix.Config
|
|||
unless File.exists?("config/host/auth_secret_test.exs") do
|
||||
Mix.raise("You need to configure your test environment.\r\n")
|
||||
end
|
||||
|
||||
import_config("auth_secret_test.exs")
|
||||
|
||||
config :farmbot, data_path: "tmp/"
|
||||
|
@ -14,12 +15,10 @@ config :farmbot, :init, [
|
|||
# Transports.
|
||||
config :farmbot, :transport, []
|
||||
|
||||
|
||||
# Configure Farmbot Behaviours.
|
||||
config :farmbot, :behaviour, [
|
||||
config :farmbot, :behaviour,
|
||||
authorization: Farmbot.Test.Authorization,
|
||||
system_tasks: Farmbot.Test.SystemTasks
|
||||
]
|
||||
|
||||
config :farmbot, Farmbot.Repo.A,
|
||||
adapter: Sqlite.Ecto2,
|
||||
|
|
|
@ -29,15 +29,14 @@ config :farmbot, :transport, [
|
|||
]
|
||||
|
||||
# Configure Farmbot Behaviours.
|
||||
config :farmbot, :behaviour, [
|
||||
config :farmbot, :behaviour,
|
||||
authorization: Farmbot.Bootstrap.Authorization,
|
||||
system_tasks: Farmbot.Target.SystemTasks,
|
||||
firmware_handler: Farmbot.Firmware.UartHandler
|
||||
]
|
||||
|
||||
config :nerves_firmware_ssh,
|
||||
authorized_keys: [
|
||||
File.read!(Path.join(System.user_home!, ".ssh/id_rsa.pub"))
|
||||
File.read!(Path.join(System.user_home!(), ".ssh/id_rsa.pub"))
|
||||
]
|
||||
|
||||
config :bootloader,
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use Mix.Config
|
||||
import_config("dev.exs")
|
||||
|
||||
config :nerves_firmware_ssh,
|
||||
authorized_keys: []
|
||||
config :nerves_firmware_ssh, authorized_keys: []
|
||||
|
||||
config :bootloader,
|
||||
init: [:nerves_runtime],
|
||||
|
|
|
@ -32,20 +32,24 @@ defmodule Farmbot do
|
|||
require Logger
|
||||
use Supervisor
|
||||
|
||||
@version Mix.Project.config[:version]
|
||||
@commit Mix.Project.config[:commit]
|
||||
@version Mix.Project.config()[:version]
|
||||
@commit Mix.Project.config()[:commit]
|
||||
|
||||
@doc """
|
||||
Entry Point to Farmbot
|
||||
"""
|
||||
def start(type, start_opts)
|
||||
|
||||
def start(_, start_opts) do
|
||||
Logger.info ">> Booting Farmbot OS version: #{@version} - #{@commit}"
|
||||
Logger.info(">> Booting Farmbot OS version: #{@version} - #{@commit}")
|
||||
name = Keyword.get(start_opts, :name, __MODULE__)
|
||||
case Supervisor.start_link(__MODULE__, [], [name: name]) do
|
||||
{:ok, pid} -> {:ok, pid}
|
||||
|
||||
case Supervisor.start_link(__MODULE__, [], name: name) do
|
||||
{:ok, pid} ->
|
||||
{:ok, pid}
|
||||
|
||||
error ->
|
||||
Logger.error "Uncaught startup error!"
|
||||
Logger.error("Uncaught startup error!")
|
||||
Farmbot.System.factory_reset(error)
|
||||
exit(error)
|
||||
end
|
||||
|
@ -53,9 +57,10 @@ defmodule Farmbot do
|
|||
|
||||
def init(args) do
|
||||
children = [
|
||||
supervisor(Farmbot.System.Supervisor, [args, [name: Farmbot.System.Supervisor ]]),
|
||||
supervisor(Farmbot.Bootstrap.Supervisor, [args, [name: Farmbot.Bootstrap.Supervisor ]]),
|
||||
supervisor(Farmbot.System.Supervisor, [args, [name: Farmbot.System.Supervisor]]),
|
||||
supervisor(Farmbot.Bootstrap.Supervisor, [args, [name: Farmbot.Bootstrap.Supervisor]])
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_one]
|
||||
supervise(children, opts)
|
||||
end
|
||||
|
|
|
@ -23,18 +23,21 @@ defmodule Farmbot.Bootstrap.Authorization do
|
|||
# It gets overwrote in the Test Environment.
|
||||
@doc "Authorizes with the farmbot api."
|
||||
def authorize(email, password, server) do
|
||||
with {:ok, rsa_key } <- fetch_rsa_key(server),
|
||||
{:ok, payload } <- build_payload(email, password, rsa_key),
|
||||
{:ok, resp } <- request_token(server, payload),
|
||||
{:ok, body } <- Poison.decode(resp),
|
||||
{:ok, map } <- Map.fetch(body, "token") do
|
||||
Map.fetch(map, "encoded")
|
||||
else
|
||||
:error -> {:error, "unknown error."}
|
||||
err ->
|
||||
require IEx; IEx.pry
|
||||
err
|
||||
end
|
||||
with {:ok, rsa_key} <- fetch_rsa_key(server),
|
||||
{:ok, payload} <- build_payload(email, password, rsa_key),
|
||||
{:ok, resp} <- request_token(server, payload),
|
||||
{:ok, body} <- Poison.decode(resp),
|
||||
{:ok, map} <- Map.fetch(body, "token") do
|
||||
Map.fetch(map, "encoded")
|
||||
else
|
||||
:error ->
|
||||
{:error, "unknown error."}
|
||||
|
||||
err ->
|
||||
require IEx
|
||||
IEx.pry()
|
||||
err
|
||||
end
|
||||
end
|
||||
|
||||
defp fetch_rsa_key(server) do
|
||||
|
@ -42,29 +45,42 @@ defmodule Farmbot.Bootstrap.Authorization do
|
|||
{:ok, {_, _, body}} ->
|
||||
r = body |> to_string() |> RSA.decode_key()
|
||||
{:ok, r}
|
||||
{:error, error} -> {:error, error}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
|
||||
defp build_payload(email, password, rsa_key) do
|
||||
secret = %{email: email, password: password, id: UUID.uuid1, version: 1}
|
||||
|> Poison.encode!
|
||||
|> RSA.encrypt({:public, rsa_key})
|
||||
|> Base.encode64
|
||||
%{user: %{credentials: secret}} |> Poison.encode
|
||||
secret =
|
||||
%{email: email, password: password, id: UUID.uuid1(), version: 1}
|
||||
|> Poison.encode!()
|
||||
|> RSA.encrypt({:public, rsa_key})
|
||||
|> Base.encode64()
|
||||
|
||||
%{user: %{credentials: secret}} |> Poison.encode()
|
||||
end
|
||||
|
||||
defp request_token(server, payload) do
|
||||
request = {'#{server}/api/tokens',
|
||||
['UserAgent', 'FarmbotOSBootstrap'],
|
||||
'application/json', payload}
|
||||
request = {
|
||||
'#{server}/api/tokens',
|
||||
['UserAgent', 'FarmbotOSBootstrap'],
|
||||
'application/json',
|
||||
payload
|
||||
}
|
||||
|
||||
case :httpc.request(:post, request, [], []) do
|
||||
{:ok, {{_, 200, _}, _, resp }} -> {:ok, resp}
|
||||
{:ok, {{_, code, _}, _, _resp }} ->
|
||||
{:error, "Failed to authorize with the Farmbot web application at: #{server} with code: #{code}"}
|
||||
{:error, error} -> {:error, error}
|
||||
end
|
||||
{:ok, {{_, 200, _}, _, resp}} ->
|
||||
{:ok, resp}
|
||||
|
||||
{:ok, {{_, code, _}, _, _resp}} ->
|
||||
{
|
||||
:error,
|
||||
"Failed to authorize with the Farmbot web application at: #{server} with code: #{code}"
|
||||
}
|
||||
|
||||
{:error, error} ->
|
||||
{:error, error}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -64,6 +64,7 @@ defmodule Farmbot.Bootstrap.Supervisor do
|
|||
authorization: Farmbot.Bootstrap.Authorization
|
||||
]
|
||||
"""
|
||||
|
||||
@auth_task Application.get_env(:farmbot, :behaviour)[:authorization] || Mix.raise(error_msg)
|
||||
|
||||
@doc "Start Bootstrap services."
|
||||
|
@ -75,21 +76,33 @@ defmodule Farmbot.Bootstrap.Supervisor do
|
|||
# try to find the creds.
|
||||
case get_creds() do
|
||||
# do the actual supervisor init if we have creds. This may still fail.
|
||||
{email, pass, server} -> actual_init(args, email, pass, server)
|
||||
{email, pass, server} ->
|
||||
actual_init(args, email, pass, server)
|
||||
|
||||
# This will cause a factory reset.
|
||||
{:error, reason} -> {:error, reason}
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
@typedoc "Authorization credentials."
|
||||
@type auth :: {Auth.email, Auth.password, Auth.server}
|
||||
@type auth :: {Auth.email(), Auth.password(), Auth.server()}
|
||||
|
||||
@spec get_creds() :: auth | {:error, term}
|
||||
defp get_creds do
|
||||
try do
|
||||
email = ConfigStorage.get_config_value(:string, "authorization", "email") || raise "No email provided."
|
||||
pass = ConfigStorage.get_config_value(:string, "authorization", "password") || raise "No password provided."
|
||||
server = ConfigStorage.get_config_value(:string, "authorization", "server") || raise "No server provided."
|
||||
email =
|
||||
ConfigStorage.get_config_value(:string, "authorization", "email") ||
|
||||
raise "No email provided."
|
||||
|
||||
pass =
|
||||
ConfigStorage.get_config_value(:string, "authorization", "password") ||
|
||||
raise "No password provided."
|
||||
|
||||
server =
|
||||
ConfigStorage.get_config_value(:string, "authorization", "server") ||
|
||||
raise "No server provided."
|
||||
|
||||
{email, pass, server}
|
||||
rescue
|
||||
e in RuntimeError -> {:error, Exception.message(e)}
|
||||
|
@ -98,30 +111,35 @@ defmodule Farmbot.Bootstrap.Supervisor do
|
|||
end
|
||||
|
||||
defp actual_init(args, email, pass, server) do
|
||||
Logger.info "Beginning authorization: #{@auth_task} - #{email} - #{server}"
|
||||
Logger.info("Beginning authorization: #{@auth_task} - #{email} - #{server}")
|
||||
# get a token
|
||||
case @auth_task.authorize(email, pass, server) do
|
||||
{:ok, token} ->
|
||||
Logger.info "Successful authorization: #{@auth_task} - #{email} - #{server}"
|
||||
Logger.info("Successful authorization: #{@auth_task} - #{email} - #{server}")
|
||||
ConfigStorage.update_config_value(:bool, "settings", "first_boot", false)
|
||||
ConfigStorage.update_config_value(:string, "authorization", "token", token)
|
||||
ConfigStorage.update_config_value(:string, "authorization", "last_shutdown_reason", nil)
|
||||
|
||||
children = [
|
||||
supervisor(Farmbot.BotState.Supervisor, [token, [name: Farmbot.BotState.Supervisor ]]),
|
||||
supervisor(Farmbot.HTTP.Supervisor, [token, [name: Farmbot.HTTP.Supervisor]]),
|
||||
supervisor(Farmbot.Repo.Supervisor, [token, [name: Farmbot.Repo.Supervisor]]),
|
||||
supervisor(Farmbot.BotState.Supervisor, [token, [name: Farmbot.BotState.Supervisor]]),
|
||||
supervisor(Farmbot.HTTP.Supervisor, [token, [name: Farmbot.HTTP.Supervisor]]),
|
||||
supervisor(Farmbot.Repo.Supervisor, [token, [name: Farmbot.Repo.Supervisor]])
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_all]
|
||||
supervise(children, opts)
|
||||
|
||||
# I don't actually _have_ to factory reset here. It would get detected ad
|
||||
# an application start fail and we would factory_reset from there,
|
||||
# the error message is just way more useful here.
|
||||
{:error, reason} ->
|
||||
Farmbot.System.factory_reset(reason)
|
||||
:ignore
|
||||
|
||||
# If we got invalid json, just try again.
|
||||
# FIXME(Connor) Why does this happen?
|
||||
{:error, :invalid, _} -> actual_init(args, email, pass, server)
|
||||
{:error, :invalid, _} ->
|
||||
actual_init(args, email, pass, server)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,16 +2,14 @@ defmodule Farmbot.BotState do
|
|||
use GenStage
|
||||
require Logger
|
||||
|
||||
defstruct [
|
||||
mcu_params: %{},
|
||||
jobs: %{},
|
||||
location_data: %{},
|
||||
pins: %{},
|
||||
configuration: %{},
|
||||
informational_settings: %{},
|
||||
user_env: %{},
|
||||
process_info: %{}
|
||||
]
|
||||
defstruct mcu_params: %{},
|
||||
jobs: %{},
|
||||
location_data: %{},
|
||||
pins: %{},
|
||||
configuration: %{},
|
||||
informational_settings: %{},
|
||||
user_env: %{},
|
||||
process_info: %{}
|
||||
|
||||
def start_link(opts) do
|
||||
GenStage.start_link(__MODULE__, [], opts)
|
||||
|
@ -32,5 +30,4 @@ defmodule Farmbot.BotState do
|
|||
state = %{state | key => Map.merge(Map.get(state, key), diff)}
|
||||
do_handle(rest, state)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -12,6 +12,7 @@ defmodule Farmbot.BotState.Supervisor do
|
|||
worker(Farmbot.Logger, [[name: Farmbot.Logger]]),
|
||||
supervisor(Farmbot.BotState.Transport.Supervisor, [])
|
||||
]
|
||||
|
||||
supervise(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,13 +10,15 @@ defmodule Farmbot.BotState.Transport.GenMQTT do
|
|||
|
||||
@doc "Start a MQTT Client."
|
||||
def start_link(device, token, server) do
|
||||
GenMQTT.start_link(__MODULE__, {device, server}, [
|
||||
reconnect_timeout: 10_000,
|
||||
username: device,
|
||||
password: token,
|
||||
timeout: 10_000,
|
||||
host: server
|
||||
])
|
||||
GenMQTT.start_link(
|
||||
__MODULE__,
|
||||
{device, server},
|
||||
reconnect_timeout: 10000,
|
||||
username: device,
|
||||
password: token,
|
||||
timeout: 10000,
|
||||
host: server
|
||||
)
|
||||
end
|
||||
|
||||
@doc "Push a bot state message."
|
||||
|
@ -38,24 +40,25 @@ defmodule Farmbot.BotState.Transport.GenMQTT do
|
|||
Failed to authenticate with the message broker!
|
||||
This is likely a problem with your server/broker configuration.
|
||||
"""
|
||||
Logger.error ">> #{msg}"
|
||||
|
||||
Logger.error(">> #{msg}")
|
||||
Farmbot.System.factory_reset(msg)
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
def on_connect_error(reason, state) do
|
||||
Logger.error ">> Failed to connect to mqtt: #{inspect reason}"
|
||||
Logger.error(">> Failed to connect to mqtt: #{inspect(reason)}")
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
def on_connect(state) do
|
||||
GenMQTT.subscribe(self(), [{bot_topic(state.device), 0}])
|
||||
Logger.info ">> Connected!"
|
||||
Logger.info(">> Connected!")
|
||||
{:ok, %{state | connected: true}}
|
||||
end
|
||||
|
||||
def on_publish(["bot", _bot, "from_clients"], msg, state) do
|
||||
Logger.warn "not implemented yet: #{inspect msg}"
|
||||
Logger.warn("not implemented yet: #{inspect(msg)}")
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
|
@ -76,9 +79,9 @@ defmodule Farmbot.BotState.Transport.GenMQTT do
|
|||
end
|
||||
|
||||
defp frontend_topic(bot), do: "bot/#{bot}/from_device"
|
||||
defp bot_topic(bot), do: "bot/#{bot}/from_clients"
|
||||
defp status_topic(bot), do: "bot/#{bot}/status"
|
||||
defp log_topic(bot), do: "bot/#{bot}/logs"
|
||||
defp bot_topic(bot), do: "bot/#{bot}/from_clients"
|
||||
defp status_topic(bot), do: "bot/#{bot}/status"
|
||||
defp log_topic(bot), do: "bot/#{bot}/logs"
|
||||
end
|
||||
|
||||
@doc "Start the MQTT Transport."
|
||||
|
@ -104,14 +107,17 @@ defmodule Farmbot.BotState.Transport.GenMQTT do
|
|||
for log <- logs do
|
||||
Client.push_bot_log(client, log)
|
||||
end
|
||||
|
||||
{:noreply, [], {internal_state, old_bot_state}}
|
||||
end
|
||||
|
||||
def handle_bot_state_events(events, {%{client: client} = internal_state, old_bot_state}) do
|
||||
new_bot_state = List.last(events)
|
||||
|
||||
if new_bot_state != old_bot_state do
|
||||
Client.push_bot_state client, new_bot_state
|
||||
Client.push_bot_state(client, new_bot_state)
|
||||
end
|
||||
|
||||
{:noreply, [], {internal_state, new_bot_state}}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,6 +11,6 @@ defmodule Farmbot.BotState.Transport.Supervisor do
|
|||
:farmbot
|
||||
|> Application.get_env(:transport)
|
||||
|> Enum.map(&worker(&1, [[name: &1]]))
|
||||
|> supervise([strategy: :one_for_one])
|
||||
|> supervise(strategy: :one_for_one)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,11 +8,10 @@ defmodule Farmbot.CeleryScript.Ast do
|
|||
|
||||
defimpl Inspect, for: __MODULE__ do
|
||||
def inspect(thing, _) do
|
||||
"#CeleryScript<#{thing.kind}: #{inspect Map.keys(thing.args)}>"
|
||||
"#CeleryScript<#{thing.kind}: #{inspect(Map.keys(thing.args))}>"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@typedoc """
|
||||
CeleryScript args.
|
||||
"""
|
||||
|
@ -22,11 +21,11 @@ defmodule Farmbot.CeleryScript.Ast do
|
|||
Type for CeleryScript Ast's.
|
||||
"""
|
||||
@type t :: %__MODULE__{
|
||||
args: args,
|
||||
body: [t, ...],
|
||||
kind: String.t,
|
||||
comment: String.t | nil
|
||||
}
|
||||
args: args,
|
||||
body: [t, ...],
|
||||
kind: String.t(),
|
||||
comment: String.t() | nil
|
||||
}
|
||||
|
||||
@enforce_keys [:args, :body, :kind]
|
||||
defstruct [:args, :body, :kind, :comment]
|
||||
|
@ -41,40 +40,35 @@ defmodule Farmbot.CeleryScript.Ast do
|
|||
def parse(%{"kind" => kind, "args" => args} = thing) do
|
||||
body = thing["body"] || []
|
||||
comment = thing["comment"]
|
||||
%__MODULE__{kind: kind,
|
||||
args: parse_args(args),
|
||||
body: parse(body),
|
||||
comment: comment}
|
||||
%__MODULE__{kind: kind, args: parse_args(args), body: parse(body), comment: comment}
|
||||
end
|
||||
|
||||
def parse(%{__struct__: _} = thing) do
|
||||
thing |> Map.from_struct |> parse
|
||||
thing |> Map.from_struct() |> parse
|
||||
end
|
||||
|
||||
def parse(%{kind: kind, args: args} = thing) do
|
||||
body = thing[:body] || []
|
||||
comment = thing[:comment]
|
||||
%__MODULE__{kind: kind,
|
||||
body: parse(body),
|
||||
args: parse_args(args),
|
||||
comment: comment}
|
||||
%__MODULE__{kind: kind, body: parse(body), args: parse_args(args), comment: comment}
|
||||
end
|
||||
|
||||
# You can give a list of nodes.
|
||||
def parse(body) when is_list(body) do
|
||||
Enum.reduce(body, [], fn(blah, acc) ->
|
||||
Enum.reduce(body, [], fn blah, acc ->
|
||||
acc ++ [parse(blah)]
|
||||
end)
|
||||
end
|
||||
|
||||
def parse(other_thing), do: raise Error, message: "#{inspect other_thing} could not be parsed as CeleryScript."
|
||||
def parse(other_thing),
|
||||
do: raise(Error, message: "#{inspect(other_thing)} could not be parsed as CeleryScript.")
|
||||
|
||||
# TODO: This is a pretty heavy memory leak, what should happen is
|
||||
# The corpus should create a bunch of atom, and then this should be
|
||||
# Strint.to_existing_atom
|
||||
@spec parse_args(map) :: map
|
||||
def parse_args(map) when is_map(map) do
|
||||
Enum.reduce(map, %{}, fn ({key, val}, acc) ->
|
||||
Enum.reduce(map, %{}, fn {key, val}, acc ->
|
||||
if is_map(val) do
|
||||
# if it is a map, it could be another node so parse it too.
|
||||
real_val = parse(val)
|
||||
|
|
|
@ -15,16 +15,16 @@ defmodule Farmbot.CeleryScript.Types do
|
|||
@type package :: binary
|
||||
|
||||
@typedoc "X | Y | Z"
|
||||
@type axis :: coord_x_bin | coord_y_bin | coord_z_bin
|
||||
@type axis :: coord_x_bin | coord_y_bin | coord_z_bin
|
||||
|
||||
@typedoc "The literal string `X`"
|
||||
@type coord_x_bin :: binary
|
||||
@type coord_x_bin :: binary
|
||||
|
||||
@typedoc "The literal string `Y`"
|
||||
@type coord_y_bin :: binary
|
||||
@type coord_y_bin :: binary
|
||||
|
||||
@typedoc "The literal string `Z`"
|
||||
@type coord_z_bin :: binary
|
||||
@type coord_z_bin :: binary
|
||||
|
||||
@typedoc "Integer representing an X coord."
|
||||
@type coord_x :: integer
|
||||
|
@ -45,10 +45,10 @@ defmodule Farmbot.CeleryScript.Types do
|
|||
}
|
||||
```
|
||||
"""
|
||||
@type pair_ast :: ast
|
||||
@type pair_ast :: ast
|
||||
|
||||
@typedoc false
|
||||
@type pairs_ast :: [pair_ast]
|
||||
@type pairs_ast :: [pair_ast]
|
||||
|
||||
@typep coord_args :: %{x: coord_x, y: coord_y, z: coord_z}
|
||||
@typedoc """
|
||||
|
@ -86,15 +86,15 @@ defmodule Farmbot.CeleryScript.Types do
|
|||
```
|
||||
"""
|
||||
|
||||
@type expl_ast_args :: %{message: binary}
|
||||
@type expl_ast_args :: %{message: binary}
|
||||
@type explanation_ast :: %Ast{kind: binary, args: expl_ast_args, body: []}
|
||||
|
||||
@typedoc "Integer representing a pin on the arduino."
|
||||
@type pin_number :: 0..69
|
||||
@type pin_number :: 0..69
|
||||
|
||||
@typedoc "Integer representing digital (0) or pwm (1)"
|
||||
@type pin_mode :: 0 | 1
|
||||
@type pin_mode :: 0 | 1
|
||||
|
||||
@typedoc false
|
||||
@type ast :: Ast.t
|
||||
@type ast :: Ast.t()
|
||||
end
|
||||
|
|
|
@ -2,33 +2,28 @@ defmodule Farmbot.CeleryScript.VirtualMachine do
|
|||
@moduledoc "Virtual Machine."
|
||||
|
||||
alias Farmbot.CeleryScript.Ast
|
||||
alias Farmbot.CeleryScript.VirtualMachine.{
|
||||
InstructionSet,
|
||||
StackFrame
|
||||
}
|
||||
alias Farmbot.CeleryScript.VirtualMachine.{InstructionSet, StackFrame}
|
||||
require Logger
|
||||
alias Farmbot.CeleryScript.VirtualMachine.RuntimeError, as: VmError
|
||||
|
||||
@typep instruction_set :: InstructionSet.t
|
||||
@typep ast :: Ast.t
|
||||
@typep stack_frame :: StackFrame.t
|
||||
@typep instruction_set :: InstructionSet.t()
|
||||
@typep ast :: Ast.t()
|
||||
@typep stack_frame :: StackFrame.t()
|
||||
|
||||
defstruct [
|
||||
instruction_set: %InstructionSet{},
|
||||
call_stack: [],
|
||||
program: [],
|
||||
pc: -1,
|
||||
running: true
|
||||
]
|
||||
defstruct instruction_set: %InstructionSet{},
|
||||
call_stack: [],
|
||||
program: [],
|
||||
pc: -1,
|
||||
running: true
|
||||
|
||||
@typedoc "State of a virtual machine."
|
||||
@type t :: %__MODULE__{
|
||||
instruction_set: instruction_set,
|
||||
call_stack: [stack_frame],
|
||||
program: [ast],
|
||||
pc: integer,
|
||||
running: boolean,
|
||||
}
|
||||
instruction_set: instruction_set,
|
||||
call_stack: [stack_frame],
|
||||
program: [ast],
|
||||
pc: integer,
|
||||
running: boolean
|
||||
}
|
||||
|
||||
# increment the program counter by one.
|
||||
defp inc_pc(%__MODULE__{pc: pc} = vm), do: %{vm | pc: pc + 1}
|
||||
|
@ -44,7 +39,7 @@ defmodule Farmbot.CeleryScript.VirtualMachine do
|
|||
defp do_step(%__MODULE__{} = vm) do
|
||||
case vm.program |> Enum.at(vm.pc) do
|
||||
%Ast{kind: kind, args: args, body: body} = ast ->
|
||||
Logger.info "Doing #{inspect ast}"
|
||||
Logger.info("Doing #{inspect(ast)}")
|
||||
|
||||
# Turn kind into an instruction
|
||||
instruction = Module.concat([kind])
|
||||
|
@ -55,6 +50,7 @@ defmodule Farmbot.CeleryScript.VirtualMachine do
|
|||
# Build a new stack frame and put it on the stack.
|
||||
sf = %StackFrame{return_address: vm.pc, args: args, body: body}
|
||||
vm = %{vm | call_stack: [sf | vm.call_stack]}
|
||||
|
||||
try do
|
||||
# Execute the implementation.
|
||||
impl.eval(vm)
|
||||
|
@ -62,7 +58,9 @@ defmodule Farmbot.CeleryScript.VirtualMachine do
|
|||
ex in VmError -> reraise(ex, System.stacktrace())
|
||||
ex -> raise VmError, machine: vm, exception: ex
|
||||
end
|
||||
nil -> %{vm | running: false}
|
||||
|
||||
nil ->
|
||||
%{vm | running: false}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,26 +3,24 @@ defmodule Farmbot.CeleryScript.VirtualMachine.InstructionSet do
|
|||
Instruction Set for a virtual machine.
|
||||
This will allow swapping of instructions between machine executions.
|
||||
"""
|
||||
|
||||
alias Farmbot.CeleryScript.VirtualMachine.UndefinedInstructionError
|
||||
|
||||
defstruct [
|
||||
instructions: %{
|
||||
# TODO(Connor) add back all the default modules here.
|
||||
}
|
||||
]
|
||||
|
||||
alias Farmbot.CeleryScript.VirtualMachine.UndefinedInstructionError
|
||||
|
||||
defstruct instructions: %{
|
||||
# TODO(Connor) add back all the default modules here.
|
||||
}
|
||||
|
||||
@typedoc "Instruction Set type."
|
||||
@type t :: %__MODULE__{
|
||||
instructions: %{optional(module) => module}
|
||||
}
|
||||
instructions: %{optional(module) => module}
|
||||
}
|
||||
|
||||
@doc false
|
||||
def fetch(%__MODULE__{instructions: instrs}, instr) do
|
||||
impl = instrs[instr] || raise UndefinedInstructionError, instr
|
||||
impl = instrs[instr] || raise UndefinedInstructionError, instr
|
||||
{:ok, impl}
|
||||
end
|
||||
|
||||
|
||||
@doc "Builds a new InstructionSet"
|
||||
@spec new :: t
|
||||
def new do
|
||||
|
@ -32,9 +30,8 @@ defmodule Farmbot.CeleryScript.VirtualMachine.InstructionSet do
|
|||
|
||||
@doc "Implement an instruction. "
|
||||
@spec impl(t, module, module) :: t
|
||||
def impl(%__MODULE__{} = set, instruction, implementation)
|
||||
when is_atom(instruction) and is_atom(implementation)
|
||||
do
|
||||
def impl(%__MODULE__{} = set, instruction, implementation)
|
||||
when is_atom(instruction) and is_atom(implementation) do
|
||||
implementation
|
||||
|> ensure_loaded!
|
||||
|> ensure_implemented!
|
||||
|
@ -42,10 +39,12 @@ defmodule Farmbot.CeleryScript.VirtualMachine.InstructionSet do
|
|||
instrs = Map.put(set.instructions, instruction, implementation)
|
||||
%{set | instructions: instrs}
|
||||
end
|
||||
|
||||
|
||||
defp ensure_loaded!(impl) do
|
||||
case Code.ensure_loaded(impl) do
|
||||
{:module, _} -> impl
|
||||
{:module, _} ->
|
||||
impl
|
||||
|
||||
{:error, _} ->
|
||||
name = Macro.underscore(impl)
|
||||
raise CompileError, description: "Failed to load implementation: #{name}."
|
||||
|
@ -57,6 +56,7 @@ defmodule Farmbot.CeleryScript.VirtualMachine.InstructionSet do
|
|||
name = Macro.underscore(impl)
|
||||
raise CompileError, description: "#{name} does not implement CeleryScript."
|
||||
end
|
||||
|
||||
impl
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
defmodule Farmbot.CeleryScript.VirtualMachine.RuntimeError do
|
||||
@moduledoc "Runtime Error of the Virtual Machine"
|
||||
|
||||
|
||||
alias Farmbot.CeleryScript.VirtualMachine
|
||||
defexception [:exception, :machine]
|
||||
|
||||
@doc "Requires a VirtualMachine state, and a message."
|
||||
def exception(opts) do
|
||||
machine =
|
||||
machine =
|
||||
case Keyword.get(opts, :machine) do
|
||||
%VirtualMachine{} = machine -> machine
|
||||
_ -> raise ArgumentError, "Machine state was not supplied to #{__MODULE__}."
|
||||
end
|
||||
exception = Keyword.get(opts, :exception) || raise ArgumentError,
|
||||
"Exception was not supplied to #{__MODULE__}."
|
||||
|
||||
exception =
|
||||
Keyword.get(opts, :exception) ||
|
||||
raise ArgumentError, "Exception was not supplied to #{__MODULE__}."
|
||||
|
||||
%__MODULE__{machine: machine, exception: exception}
|
||||
end
|
||||
|
||||
|
@ -22,7 +25,7 @@ defmodule Farmbot.CeleryScript.VirtualMachine.RuntimeError do
|
|||
end
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
exception: Exception.t,
|
||||
machine: Farmbot.CeleryScript.VirtualMachine.t
|
||||
}
|
||||
exception: Exception.t(),
|
||||
machine: Farmbot.CeleryScript.VirtualMachine.t()
|
||||
}
|
||||
end
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
defmodule Farmbot.CeleryScript.VirtualMachine.StackFrame do
|
||||
@moduledoc "Frame of the callstack"
|
||||
|
||||
|
||||
alias Farmbot.CeleryScript.Ast
|
||||
@enforce_keys [:args, :return_address, :body]
|
||||
defstruct [:args, :return_address, :body]
|
||||
|
||||
@typedoc "Part of a call stack."
|
||||
@type t :: %__MODULE__{
|
||||
args: Ast.args,
|
||||
body: Ast.body,
|
||||
return_address: integer
|
||||
}
|
||||
args: Ast.args(),
|
||||
body: Ast.body(),
|
||||
return_address: integer
|
||||
}
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ defmodule Farmbot.CeleryScript.VirtualMachine.UndefinedInstructionError do
|
|||
@moduledoc "Undefined Instruction. Usually means something is not implemented."
|
||||
|
||||
defexception [:message, :instruction]
|
||||
|
||||
|
||||
@doc false
|
||||
def exception(instruction) do
|
||||
instr = Macro.underscore(instruction)
|
||||
|
|
|
@ -2,25 +2,44 @@ defmodule Farmbot.DebugLog do
|
|||
@moduledoc false
|
||||
|
||||
@doc false
|
||||
def color(:NC), do: "\e[0m"
|
||||
def color(:WHITE), do: "\e[1;37m"
|
||||
def color(:BLACK), do: "\e[0;30m"
|
||||
def color(:BLUE), do: "\e[0;34m"
|
||||
def color(:LIGHT_BLUE), do: "\e[1;34m"
|
||||
def color(:GREEN), do: "\e[0;32m"
|
||||
def color(:LIGHT_GREEN), do: "\e[1;32m"
|
||||
def color(:CYAN), do: "\e[0;36m"
|
||||
def color(:LIGHT_CYAN), do: "\e[1;36m"
|
||||
def color(:RED), do: "\e[0;31m"
|
||||
def color(:LIGHT_RED), do: "\e[1;31m"
|
||||
def color(:PURPLE), do: "\e[0;35m"
|
||||
def color(:NC), do: "\e[0m"
|
||||
def color(:WHITE), do: "\e[1;37m"
|
||||
def color(:BLACK), do: "\e[0;30m"
|
||||
def color(:BLUE), do: "\e[0;34m"
|
||||
def color(:LIGHT_BLUE), do: "\e[1;34m"
|
||||
def color(:GREEN), do: "\e[0;32m"
|
||||
def color(:LIGHT_GREEN), do: "\e[1;32m"
|
||||
def color(:CYAN), do: "\e[0;36m"
|
||||
def color(:LIGHT_CYAN), do: "\e[1;36m"
|
||||
def color(:RED), do: "\e[0;31m"
|
||||
def color(:LIGHT_RED), do: "\e[1;31m"
|
||||
def color(:PURPLE), do: "\e[0;35m"
|
||||
def color(:LIGHT_PURPLE), do: "\e[1;35m"
|
||||
def color(:BROWN), do: "\e[0;33m"
|
||||
def color(:YELLOW), do: "\e[1;33m"
|
||||
def color(:GRAY), do: "\e[0;30m"
|
||||
def color(:LIGHT_GRAY), do: "\e[0;37m"
|
||||
def color(:BROWN), do: "\e[0;33m"
|
||||
def color(:YELLOW), do: "\e[1;33m"
|
||||
def color(:GRAY), do: "\e[0;30m"
|
||||
def color(:LIGHT_GRAY), do: "\e[0;37m"
|
||||
|
||||
def color(:RANDOM) do
|
||||
Enum.random([:NC, :WHITE, :BLACK, :BLUE, :LIGHT_BLUE, :GREEN, :LIGHT_GREEN, :CYAN, :LIGHT_CYAN, :RED, :LIGHT_RED, :PURPLE, :LIGHT_PURPLE, :BROWN, :YELLOW, :GRAY, :LIGHT_GRAY])
|
||||
Enum.random([
|
||||
:NC,
|
||||
:WHITE,
|
||||
:BLACK,
|
||||
:BLUE,
|
||||
:LIGHT_BLUE,
|
||||
:GREEN,
|
||||
:LIGHT_GREEN,
|
||||
:CYAN,
|
||||
:LIGHT_CYAN,
|
||||
:RED,
|
||||
:LIGHT_RED,
|
||||
:PURPLE,
|
||||
:LIGHT_PURPLE,
|
||||
:BROWN,
|
||||
:YELLOW,
|
||||
:GRAY,
|
||||
:LIGHT_GRAY
|
||||
])
|
||||
|> color()
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ defmodule Farmbot.Firmware do
|
|||
|
||||
use GenStage
|
||||
require Logger
|
||||
|
||||
|
||||
defmodule Handler do
|
||||
@moduledoc """
|
||||
Any module that implements this behaviour should be a GenStage.
|
||||
|
@ -13,19 +13,19 @@ defmodule Farmbot.Firmware do
|
|||
will subscribe_to: the implementing handler. Events should be
|
||||
Gcodes as parsed by `Farmbot.Firmware.Gcode.Parser`.
|
||||
"""
|
||||
|
||||
|
||||
@doc "Start a firmware handler."
|
||||
@callback start_link :: GenServer.on_start
|
||||
@callback start_link :: GenServer.on_start()
|
||||
|
||||
@doc "Write a gcode."
|
||||
@callback write(Farmbot.Firmware.Gcode.t) :: :ok | {:error, term}
|
||||
@callback write(Farmbot.Firmware.Gcode.t()) :: :ok | {:error, term}
|
||||
end
|
||||
|
||||
@handler Application.get_env(:farmbot, :behaviour)[:firmware_handler] || raise "No fw handler."
|
||||
|
||||
@handler Application.get_env(:farmbot, :behaviour)[:firmware_handler] || raise("No fw handler.")
|
||||
|
||||
@doc "Start the firmware services."
|
||||
def start_link do
|
||||
GenStage.start_link(__MODULE__, [], [name: __MODULE__])
|
||||
GenStage.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
@doc "Writes a Gcode to a the running hand:ler"
|
||||
|
@ -41,8 +41,10 @@ defmodule Farmbot.Firmware do
|
|||
|
||||
def handle_gcodes(codes, acc \\ [])
|
||||
def handle_gcodes([], acc), do: Enum.reverse(acc)
|
||||
|
||||
def handle_gcodes([code | rest], acc) do
|
||||
res = handle_gcode(code)
|
||||
|
||||
if res do
|
||||
handle_gcodes(rest, [res | acc])
|
||||
else
|
||||
|
@ -55,7 +57,7 @@ defmodule Farmbot.Firmware do
|
|||
end
|
||||
|
||||
def handle_gcode(code) do
|
||||
Logger.warn "unhandled code: #{inspect code}"
|
||||
Logger.warn("unhandled code: #{inspect(code)}")
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
defmodule Farmbot.Firmware.Gcode do
|
||||
@moduledoc """
|
||||
Gcode is the itermediate representation
|
||||
of commands to the underlying hardware.
|
||||
of commands to the underlying hardware.
|
||||
"""
|
||||
@typedoc "Code representation."
|
||||
@typedoc "Code representation."
|
||||
@type t :: term
|
||||
end
|
||||
|
|
|
@ -13,11 +13,12 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
def parse_code("R03 Q" <> tag), do: {tag, :error}
|
||||
def parse_code("R04 Q" <> tag), do: {tag, :busy}
|
||||
|
||||
def parse_code("R05" <> _r), do: {nil, :noop} # Dont care about this.
|
||||
# Dont care about this.
|
||||
def parse_code("R05" <> _r), do: {nil, :noop}
|
||||
def parse_code("R06 " <> r), do: parse_report_calibration(r)
|
||||
def parse_code("R07 " <> _), do: {nil, :noop}
|
||||
|
||||
def parse_code("R20 Q" <> tag), do: {tag, :report_params_complete}
|
||||
def parse_code("R20 Q" <> tag), do: {tag, :report_params_complete}
|
||||
def parse_code("R21 " <> params), do: parse_pvq(params, :report_parameter_value)
|
||||
def parse_code("R23 " <> params), do: parse_report_axis_calibration(params)
|
||||
def parse_code("R31 " <> params), do: parse_pvq(params, :report_status_value)
|
||||
|
@ -27,19 +28,26 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
def parse_code("R82 " <> p), do: report_xyz(p, :report_current_position)
|
||||
def parse_code("R83 " <> v), do: parse_version(v)
|
||||
|
||||
def parse_code("R84 " <> p), do: report_xyz(p, :report_encoder_position_scaled)
|
||||
def parse_code("R85 " <> p), do: report_xyz(p, :report_encoder_position_raw)
|
||||
def parse_code("R84 " <> p), do: report_xyz(p, :report_encoder_position_scaled)
|
||||
def parse_code("R85 " <> p), do: report_xyz(p, :report_encoder_position_raw)
|
||||
def parse_code("R87 Q" <> q), do: {q, :report_emergency_lock}
|
||||
|
||||
def parse_code("R99 " <> message) do {nil, {:debug_message, message}} end
|
||||
def parse_code("Command" <> _), do: {nil, :noop} # I think this is a bug
|
||||
def parse_code(code) do {:unhandled_gcode, code} end
|
||||
def parse_code("R99 " <> message) do
|
||||
{nil, {:debug_message, message}}
|
||||
end
|
||||
|
||||
@spec parse_report_calibration(binary)
|
||||
:: {binary, {:report_calibration, binary, binary}}
|
||||
# I think this is a bug
|
||||
def parse_code("Command" <> _), do: {nil, :noop}
|
||||
|
||||
def parse_code(code) do
|
||||
{:unhandled_gcode, code}
|
||||
end
|
||||
|
||||
@spec parse_report_calibration(binary) :: {binary, {:report_calibration, binary, binary}}
|
||||
defp parse_report_calibration(r) do
|
||||
[axis_and_status | [q]] = String.split(r, " Q")
|
||||
<<a :: size(8), b :: size(8)>> = axis_and_status
|
||||
<<a::size(8), b::size(8)>> = axis_and_status
|
||||
|
||||
case <<b>> do
|
||||
"0" -> {q, {:report_calibration, <<a>>, :idle}}
|
||||
"1" -> {q, {:report_calibration, <<a>>, :home}}
|
||||
|
@ -49,8 +57,9 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
|
||||
defp parse_report_axis_calibration(params) do
|
||||
["P" <> parm, "V" <> val, "Q" <> tag] = String.split(params, " ")
|
||||
|
||||
if parm in ["141", "142", "143"] do
|
||||
uh = :report_axis_calibration
|
||||
uh = :report_axis_calibration
|
||||
msg = {uh, parse_param(parm), String.to_integer(val)}
|
||||
{tag, msg}
|
||||
else
|
||||
|
@ -64,20 +73,17 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
{code, {:report_software_version, derp}}
|
||||
end
|
||||
|
||||
@type reporter :: :report_current_position
|
||||
| :report_encoder_position_scaled
|
||||
| :report_encoder_position_raw
|
||||
@type reporter ::
|
||||
:report_current_position
|
||||
| :report_encoder_position_scaled
|
||||
| :report_encoder_position_raw
|
||||
|
||||
@spec report_xyz(binary, reporter)
|
||||
:: {binary, {reporter, binary, binary, binary}}
|
||||
@spec report_xyz(binary, reporter) :: {binary, {reporter, binary, binary, binary}}
|
||||
defp report_xyz(position, reporter) when is_bitstring(position),
|
||||
do: position |> String.split(" ") |> do_parse_pos(reporter)
|
||||
|
||||
defp do_parse_pos(["X" <> x, "Y" <> y, "Z" <> z, "Q" <> tag], reporter) do
|
||||
{tag, {reporter,
|
||||
String.to_integer(x),
|
||||
String.to_integer(y),
|
||||
String.to_integer(z)}}
|
||||
{tag, {reporter, String.to_integer(x), String.to_integer(y), String.to_integer(z)}}
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
|
@ -86,33 +92,37 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
iex> Gcode.parse_end_stops("XA1 XB1 YA0 YB1 ZA0 ZB1 Q123")
|
||||
{:report_end_stops, "1", "1", "0", "1", "0", "1", "123"}
|
||||
"""
|
||||
@spec parse_end_stops(binary)
|
||||
:: {:report_end_stops,
|
||||
binary,
|
||||
binary,
|
||||
binary,
|
||||
binary,
|
||||
binary,
|
||||
binary,
|
||||
binary}
|
||||
def parse_end_stops(
|
||||
<<
|
||||
"XA", xa :: size(8), 32,
|
||||
"XB", xb :: size(8), 32,
|
||||
"YA", ya :: size(8), 32,
|
||||
"YB", yb :: size(8), 32,
|
||||
"ZA", za :: size(8), 32,
|
||||
"ZB", zb :: size(8), 32,
|
||||
"Q", tag :: binary
|
||||
>>), do: {tag, {:report_end_stops,
|
||||
xa |> pes,
|
||||
xb |> pes,
|
||||
ya |> pes,
|
||||
yb |> pes,
|
||||
za |> pes,
|
||||
zb |> pes}}
|
||||
@spec parse_end_stops(binary) ::
|
||||
{:report_end_stops, binary, binary, binary, binary, binary, binary, binary}
|
||||
def parse_end_stops(<<
|
||||
"XA",
|
||||
xa::size(8),
|
||||
32,
|
||||
"XB",
|
||||
xb::size(8),
|
||||
32,
|
||||
"YA",
|
||||
ya::size(8),
|
||||
32,
|
||||
"YB",
|
||||
yb::size(8),
|
||||
32,
|
||||
"ZA",
|
||||
za::size(8),
|
||||
32,
|
||||
"ZB",
|
||||
zb::size(8),
|
||||
32,
|
||||
"Q",
|
||||
tag::binary
|
||||
>>),
|
||||
do: {
|
||||
tag,
|
||||
{:report_end_stops, xa |> pes, xb |> pes, ya |> pes, yb |> pes, za |> pes, zb |> pes}
|
||||
}
|
||||
|
||||
@spec pes(48 | 49) :: 0 | 1 # lol
|
||||
# lol
|
||||
@spec pes(48 | 49) :: 0 | 1
|
||||
defp pes(48), do: 0
|
||||
defp pes(49), do: 1
|
||||
|
||||
|
@ -126,24 +136,21 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
iex> Gcode.parse_pvq("P20 V100 Q12", :report_parameter_value)
|
||||
{:report_parameter_value, "20" ,"100", "12"}
|
||||
"""
|
||||
@spec parse_pvq(binary, :report_parameter_value)
|
||||
:: {:report_parameter_value, atom, integer, String.t}
|
||||
@spec parse_pvq(binary, :report_parameter_value) ::
|
||||
{:report_parameter_value, atom, integer, String.t()}
|
||||
def parse_pvq(params, :report_parameter_value)
|
||||
when is_bitstring(params),
|
||||
do: params |> String.split(" ") |> do_parse_params
|
||||
when is_bitstring(params),
|
||||
do: params |> String.split(" ") |> do_parse_params
|
||||
|
||||
def parse_pvq(params, human_readable_param_name)
|
||||
when is_bitstring(params)
|
||||
and is_atom(human_readable_param_name),
|
||||
do: params |> String.split(" ") |> do_parse_pvq(human_readable_param_name)
|
||||
when is_bitstring(params) and is_atom(human_readable_param_name),
|
||||
do: params |> String.split(" ") |> do_parse_pvq(human_readable_param_name)
|
||||
|
||||
defp do_parse_pvq([p, v, q], human_readable_param_name) do
|
||||
[_, rp] = String.split(p, "P")
|
||||
[_, rv] = String.split(v, "V")
|
||||
[_, rq] = String.split(q, "Q")
|
||||
{rq, {human_readable_param_name,
|
||||
String.to_integer(rp),
|
||||
String.to_integer(rv)}}
|
||||
{rq, {human_readable_param_name, String.to_integer(rp), String.to_integer(rv)}}
|
||||
end
|
||||
|
||||
defp do_parse_params([p, v, q]) do
|
||||
|
@ -293,7 +300,6 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
def parse_param(:param_e_stop_on_mov_err), do: 4
|
||||
def parse_param(:param_mov_nr_retry), do: 5
|
||||
|
||||
|
||||
def parse_param(:movement_timeout_x), do: 11
|
||||
def parse_param(:movement_timeout_y), do: 12
|
||||
def parse_param(:movement_timeout_z), do: 13
|
||||
|
@ -398,13 +404,16 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
def parse_param(:pin_guard_5_active_state), do: 223
|
||||
|
||||
def parse_param(param_string) when is_bitstring(param_string),
|
||||
do: param_string |> String.to_atom |> parse_param
|
||||
do: param_string |> String.to_atom() |> parse_param
|
||||
|
||||
# derp.
|
||||
if Mix.env == :dev do
|
||||
if Mix.env() == :dev do
|
||||
def parse_param(uhh) do
|
||||
Logger.error("Unrecognized param needs implementation " <>
|
||||
"#{inspect uhh}", rollbar: false)
|
||||
Logger.error(
|
||||
"Unrecognized param needs implementation " <> "#{inspect(uhh)}",
|
||||
rollbar: false
|
||||
)
|
||||
|
||||
nil
|
||||
end
|
||||
else
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Farmbot.Firmware.StubHandler do
|
|||
|
||||
def start_link do
|
||||
Logger.warn("Firmware is being stubbed.")
|
||||
GenStage.start_link(__MODULE__, [], [name: __MODULE__])
|
||||
GenStage.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
def write(code) do
|
||||
|
|
|
@ -11,10 +11,12 @@ defmodule Farmbot.Firmware.Supervisor do
|
|||
|
||||
def init([]) do
|
||||
handler_mod = Application.get_env(:farmbot, :behaviour)[:firmware_handler] || raise @error_msg
|
||||
|
||||
children = [
|
||||
worker(handler_mod, []),
|
||||
worker(Farmbot.Firmware, [])
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_one]
|
||||
supervise(children, opts)
|
||||
end
|
||||
|
|
|
@ -16,7 +16,7 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
|
||||
@doc "Starts a UART Firmware Handler."
|
||||
def start_link do
|
||||
GenStage.start_link(__MODULE__, [], [name: __MODULE__])
|
||||
GenStage.start_link(__MODULE__, [], name: __MODULE__)
|
||||
end
|
||||
|
||||
## Private
|
||||
|
@ -31,12 +31,15 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
|
||||
def init([]) do
|
||||
# If in dev environment, it is expected that this be done at compile time.
|
||||
# If ini target environment, this should be done by `Farmbot.Firmware.AutoDetector`.
|
||||
tty = Application.get_env(:farmbot, :uart_handler)[:tty] || raise "Please configure uart handler!"
|
||||
# If ini target environment, this should be done by `Farmbot.Firmware.AutoDetector`.
|
||||
tty =
|
||||
Application.get_env(:farmbot, :uart_handler)[:tty] || raise "Please configure uart handler!"
|
||||
|
||||
{:ok, nerves} = UART.start_link()
|
||||
Process.link(nerves)
|
||||
|
||||
case open_tty(nerves, tty) do
|
||||
:ok -> {:producer, %State{nerves: nerves, codes: []}}
|
||||
:ok -> {:producer, %State{nerves: nerves, codes: []}}
|
||||
err -> {:stop, err, :no_state}
|
||||
end
|
||||
end
|
||||
|
@ -48,15 +51,19 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
# Flush the buffers so we start fresh
|
||||
:ok = UART.flush(nerves)
|
||||
:ok
|
||||
err -> err
|
||||
|
||||
err ->
|
||||
err
|
||||
end
|
||||
end
|
||||
|
||||
defp configure_uart(nerves, active) do
|
||||
UART.configure(nerves,
|
||||
UART.configure(
|
||||
nerves,
|
||||
framing: {UART.Framing.Line, separator: "\r\n"},
|
||||
active: active,
|
||||
rx_framing_timeout: 500)
|
||||
rx_framing_timeout: 500
|
||||
)
|
||||
end
|
||||
|
||||
# if there is an error, we assume something bad has happened, and we probably
|
||||
|
@ -86,5 +93,4 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
defp do_dispatch(codes, state) do
|
||||
{:noreply, Enum.reverse(codes), %{state | codes: []}}
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
defmodule Farmbot.Firmware.UartHandler.AutoDetector do
|
||||
@moduledoc "Helper for autodetecting and configuring a UART fw device."
|
||||
|
||||
end
|
||||
|
|
|
@ -26,12 +26,10 @@ defmodule Farmbot.Firmware.UartHandler.Framinig do
|
|||
|
||||
defmodule State do
|
||||
@moduledoc false
|
||||
defstruct [
|
||||
max_length: nil,
|
||||
separator: nil,
|
||||
processed: <<>>,
|
||||
in_process: <<>>
|
||||
]
|
||||
defstruct max_length: nil,
|
||||
separator: nil,
|
||||
processed: <<>>,
|
||||
in_process: <<>>
|
||||
end
|
||||
|
||||
def init(args) do
|
||||
|
@ -48,11 +46,15 @@ defmodule Farmbot.Firmware.UartHandler.Framinig do
|
|||
|
||||
def remove_framing(data, state) do
|
||||
{new_processed, new_in_process, lines} =
|
||||
process_data(state.separator,
|
||||
byte_size(state.separator),
|
||||
state.max_length,
|
||||
state.processed,
|
||||
state.in_process <> data, [])
|
||||
process_data(
|
||||
state.separator,
|
||||
byte_size(state.separator),
|
||||
state.max_length,
|
||||
state.processed,
|
||||
state.in_process <> data,
|
||||
[]
|
||||
)
|
||||
|
||||
new_state = %{state | processed: new_processed, in_process: new_in_process}
|
||||
rc = if buffer_empty?(new_state), do: :ok, else: :in_frame
|
||||
{rc, lines, new_state}
|
||||
|
@ -67,6 +69,7 @@ defmodule Farmbot.Firmware.UartHandler.Framinig do
|
|||
def flush(direction, state) when direction == :receive or direction == :both do
|
||||
%{state | processed: <<>>, in_process: <<>>}
|
||||
end
|
||||
|
||||
def flush(_direction, state) do
|
||||
state
|
||||
end
|
||||
|
@ -77,7 +80,7 @@ defmodule Farmbot.Firmware.UartHandler.Framinig do
|
|||
|
||||
# Handle not enough data case
|
||||
defp process_data(_separator, sep_length, _max_length, processed, to_process, lines)
|
||||
when byte_size(to_process) < sep_length do
|
||||
when byte_size(to_process) < sep_length do
|
||||
{processed, to_process, lines}
|
||||
end
|
||||
|
||||
|
@ -88,10 +91,12 @@ defmodule Farmbot.Firmware.UartHandler.Framinig do
|
|||
<<^separator::binary-size(sep_length), rest::binary>> ->
|
||||
new_lines = lines ++ [parse_code(processed)]
|
||||
process_data(separator, sep_length, max_length, <<>>, rest, new_lines)
|
||||
|
||||
# Handle line too long case
|
||||
to_process when byte_size(processed) == max_length and to_process != <<>> ->
|
||||
new_lines = lines ++ [{:partial, processed}]
|
||||
process_data(separator, sep_length, max_length, <<>>, to_process, new_lines)
|
||||
|
||||
# Handle next char
|
||||
<<next_char::binary-size(1), rest::binary>> ->
|
||||
process_data(separator, sep_length, max_length, processed <> next_char, rest, lines)
|
||||
|
|
|
@ -8,7 +8,7 @@ defmodule Farmbot.HTTP.Helpers do
|
|||
"""
|
||||
defmacro is_2xx(number) do
|
||||
quote do
|
||||
(unquote(number) > 199) and (unquote(number) < 300)
|
||||
unquote(number) > 199 and unquote(number) < 300
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,21 +2,15 @@ defmodule Farmbot.HTTP do
|
|||
@moduledoc """
|
||||
Farmbot HTTP adapter for accessing the world and farmbot web api easily.
|
||||
"""
|
||||
use GenServer
|
||||
alias HTTPoison
|
||||
alias HTTPoison.{
|
||||
AsyncResponse,
|
||||
AsyncStatus,
|
||||
AsyncHeaders,
|
||||
AsyncChunk,
|
||||
AsyncEnd,
|
||||
}
|
||||
alias Farmbot.HTTP.{Response, Helpers, Error}
|
||||
import Helpers
|
||||
use GenServer
|
||||
alias HTTPoison
|
||||
alias HTTPoison.{AsyncResponse, AsyncStatus, AsyncHeaders, AsyncChunk, AsyncEnd}
|
||||
alias Farmbot.HTTP.{Response, Helpers, Error}
|
||||
import Helpers
|
||||
require Logger
|
||||
|
||||
@version Mix.Project.config[:version]
|
||||
@target Mix.Project.config[:target]
|
||||
@version Mix.Project.config()[:version]
|
||||
@target Mix.Project.config()[:target]
|
||||
@redirect_status_codes [301, 302, 303, 307, 308]
|
||||
|
||||
@doc """
|
||||
|
@ -46,7 +40,7 @@ defmodule Farmbot.HTTP do
|
|||
{:ok, %Response{status_code: code} = resp} when is_2xx(code) -> resp
|
||||
{:ok, %Response{} = resp} -> raise Error, resp
|
||||
{:error, error} when is_binary(error) or is_atom(error) -> raise Error, "#{error}"
|
||||
{:error, error} -> raise Error, inspect error
|
||||
{:error, error} -> raise Error, inspect(error)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -57,40 +51,46 @@ defmodule Farmbot.HTTP do
|
|||
HTTP #{verb} request.
|
||||
"""
|
||||
def unquote(verb)(http, url, body \\ "", headers \\ [], opts \\ [])
|
||||
|
||||
def unquote(verb)(http, url, body, headers, opts) do
|
||||
request(http, unquote(verb), url, body, headers, opts)
|
||||
request(http, unquote(verb), url, body, headers, opts)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Same as #{verb}/5 but raises Farmbot.HTTP.Error exception.
|
||||
"""
|
||||
fun_name = "#{verb}!" |> String.to_atom
|
||||
fun_name = "#{verb}!" |> String.to_atom()
|
||||
def unquote(fun_name)(http, url, body \\ "", headers \\ [], opts \\ [])
|
||||
|
||||
def unquote(fun_name)(http, url, body, headers, opts) do
|
||||
request!(http, unquote(verb), url, body, headers, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def download_file(http, url, path, progress_callback \\ nil, payload \\ "", headers \\ [])
|
||||
|
||||
def download_file(http, url, path, progress_callback, payload, headers) do
|
||||
case get(http, url, payload, headers, file: path, progress_callback: progress_callback) do
|
||||
{:ok, %Response{status_code: code}} when is_2xx(code) -> {:ok, path}
|
||||
{:ok, %Response{} = resp} -> {:error, resp}
|
||||
{:error, reason} -> {:error, reason}
|
||||
{:error, reason} -> {:error, reason}
|
||||
end
|
||||
end
|
||||
|
||||
def download_file!(http, url, path, progress_callback \\ nil, payload \\ "", headers \\ [])
|
||||
|
||||
def download_file!(http, url, path, progress_callback, payload, headers) do
|
||||
case download_file(http, url, path, progress_callback, payload, headers) do
|
||||
{:ok, path} -> path
|
||||
{:ok, path} -> path
|
||||
{:error, reason} -> raise Error, reason
|
||||
end
|
||||
end
|
||||
|
||||
def upload_file(http, path, meta \\ nil) do
|
||||
if File.exists?(path) do
|
||||
http |> get("/api/storage_auth") |> do_multipart_request(http, meta || %{x: -1, y: -1, z: -1}, path)
|
||||
http
|
||||
|> get("/api/storage_auth")
|
||||
|> do_multipart_request(http, meta || %{x: -1, y: -1, z: -1}, path)
|
||||
else
|
||||
{:error, "#{path} not found"}
|
||||
end
|
||||
|
@ -103,20 +103,27 @@ defmodule Farmbot.HTTP do
|
|||
end
|
||||
end
|
||||
|
||||
defp do_multipart_request({:ok, %Response{status_code: code, body: bin_body}}, http, meta, path) when is_2xx(code) do
|
||||
defp do_multipart_request({:ok, %Response{status_code: code, body: bin_body}}, http, meta, path)
|
||||
when is_2xx(code) do
|
||||
with {:ok, body} <- Poison.decode(bin_body),
|
||||
{:ok, file} <- File.read(path) do
|
||||
url = "https:" <> body["url"]
|
||||
form_data = body["form_data"]
|
||||
attachment_url = url <> form_data["key"]
|
||||
mp = Enum.map(form_data, fn({key, val}) -> if key == "file", do: {"file", file}, else: {key, val} end)
|
||||
http
|
||||
|> post(url, {:multipart, mp})
|
||||
|> finish_upload(http, attachment_url, meta)
|
||||
end
|
||||
url = "https:" <> body["url"]
|
||||
form_data = body["form_data"]
|
||||
attachment_url = url <> form_data["key"]
|
||||
|
||||
mp =
|
||||
Enum.map(form_data, fn {key, val} ->
|
||||
if key == "file", do: {"file", file}, else: {key, val}
|
||||
end)
|
||||
|
||||
http
|
||||
|> post(url, {:multipart, mp})
|
||||
|> finish_upload(http, attachment_url, meta)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_multipart_request({:ok, %Response{} = response}, _http, _meta, _path), do: {:error, response}
|
||||
defp do_multipart_request({:ok, %Response{} = response}, _http, _meta, _path),
|
||||
do: {:error, response}
|
||||
|
||||
defp do_multipart_request({:error, reason}, _http, _meta, _path), do: {:error, reason}
|
||||
|
||||
|
@ -126,26 +133,42 @@ defmodule Farmbot.HTTP do
|
|||
{:ok, %Response{status_code: code}} when is_2xx(code) ->
|
||||
# debug_log("#{atch_url} should exist shortly.")
|
||||
:ok
|
||||
{:ok, %Response{} = response} -> {:error, response}
|
||||
{:error, reason} -> {:error, reason}
|
||||
|
||||
{:ok, %Response{} = response} ->
|
||||
{:error, response}
|
||||
|
||||
{:error, reason} ->
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp finish_upload({:ok, %Response{} = resp}, _http, _url, _meta), do: {:error, resp}
|
||||
defp finish_upload({:error, reason}, _http, _url, _meta), do: {:error, reason}
|
||||
defp finish_upload({:error, reason}, _http, _url, _meta), do: {:error, reason}
|
||||
|
||||
# GenServer
|
||||
|
||||
defmodule State do
|
||||
defstruct [:token, :requests]
|
||||
|
||||
defimpl Inspect, for: __MODULE__ do
|
||||
def inspect(_state, _), do: "#HTTPState<>"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Buffer do
|
||||
defstruct [:data, :headers, :status_code, :request, :from, :id, :file, :timeout, :progress_callback, :file_size]
|
||||
defstruct [
|
||||
:data,
|
||||
:headers,
|
||||
:status_code,
|
||||
:request,
|
||||
:from,
|
||||
:id,
|
||||
:file,
|
||||
:timeout,
|
||||
:progress_callback,
|
||||
:file_size
|
||||
]
|
||||
end
|
||||
|
||||
def start_link(token, opts) do
|
||||
|
@ -159,22 +182,24 @@ defmodule Farmbot.HTTP do
|
|||
|
||||
def handle_call({:req, method, url, body, headers, opts}, from, state) do
|
||||
{file, opts} = maybe_open_file(opts)
|
||||
opts = fb_opts(opts)
|
||||
headers = fb_headers(headers)
|
||||
opts = fb_opts(opts)
|
||||
headers = fb_headers(headers)
|
||||
# debug_log "#{inspect Tuple.delete_at(from, 0)} Request start (#{url})"
|
||||
# Pattern match the url.
|
||||
case url do
|
||||
"/api" <> _ -> do_api_request({method, url, body, headers, opts, from}, state)
|
||||
_ -> do_normal_request({method, url, body, headers, opts, from}, file, state)
|
||||
_ -> do_normal_request({method, url, body, headers, opts, from}, file, state)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({:timeout, ref}, state) do
|
||||
case state.requests[ref] do
|
||||
%Buffer{} = buffer ->
|
||||
GenServer.reply buffer.from, {:error, :timeout}
|
||||
GenServer.reply(buffer.from, {:error, :timeout})
|
||||
{:noreply, %{state | requests: Map.delete(state.requests, ref)}}
|
||||
nil -> {:noreply, state}
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -184,7 +209,9 @@ defmodule Farmbot.HTTP do
|
|||
# debug_log "#{inspect Tuple.delete_at(buffer.from, 0)} Got Status."
|
||||
HTTPoison.stream_next(%AsyncResponse{id: ref})
|
||||
{:noreply, %{state | requests: %{state.requests | ref => %{buffer | status_code: code}}}}
|
||||
nil -> {:noreply, state}
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -192,16 +219,30 @@ defmodule Farmbot.HTTP do
|
|||
case state.requests[ref] do
|
||||
%Buffer{} = buffer ->
|
||||
# debug_log("#{inspect Tuple.delete_at(buffer.from, 0)} Got headers")
|
||||
file_size = Enum.find_value(headers, fn({header, val}) ->
|
||||
case header do
|
||||
"Content-Length" -> val
|
||||
"content_length" -> val
|
||||
_header -> nil
|
||||
end
|
||||
end)
|
||||
file_size =
|
||||
Enum.find_value(headers, fn {header, val} ->
|
||||
case header do
|
||||
"Content-Length" -> val
|
||||
"content_length" -> val
|
||||
_header -> nil
|
||||
end
|
||||
end)
|
||||
|
||||
HTTPoison.stream_next(%AsyncResponse{id: ref})
|
||||
{:noreply, %{state | requests: %{state.requests | ref => %{buffer | headers: headers, file_size: file_size}}}}
|
||||
nil -> {:noreply, state}
|
||||
|
||||
{
|
||||
:noreply,
|
||||
%{
|
||||
state
|
||||
| requests: %{
|
||||
state.requests
|
||||
| ref => %{buffer | headers: headers, file_size: file_size}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -209,12 +250,24 @@ defmodule Farmbot.HTTP do
|
|||
case state.requests[ref] do
|
||||
%Buffer{} = buffer ->
|
||||
if buffer.timeout, do: Process.cancel_timer(buffer.timeout)
|
||||
timeout = Process.send_after(self(), {:timeout, ref}, 30_000)
|
||||
timeout = Process.send_after(self(), {:timeout, ref}, 30000)
|
||||
maybe_log_progress(buffer)
|
||||
maybe_stream_to_file(buffer.file, buffer.status_code, chunk)
|
||||
HTTPoison.stream_next(%AsyncResponse{id: ref})
|
||||
{:noreply, %{state | requests: %{state.requests | ref => %{buffer | data: buffer.data <> chunk, timeout: timeout}}}}
|
||||
nil -> {:noreply, state}
|
||||
|
||||
{
|
||||
:noreply,
|
||||
%{
|
||||
state
|
||||
| requests: %{
|
||||
state.requests
|
||||
| ref => %{buffer | data: buffer.data <> chunk, timeout: timeout}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -223,7 +276,9 @@ defmodule Farmbot.HTTP do
|
|||
%Buffer{} = buffer ->
|
||||
# debug_log "#{inspect Tuple.delete_at(buffer.from, 0)} Request finish."
|
||||
finish_request(buffer, state)
|
||||
nil -> {:noreply, state}
|
||||
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -232,26 +287,30 @@ defmodule Farmbot.HTTP do
|
|||
def terminate(reason, state) do
|
||||
for {_ref, buffer} <- state.requests do
|
||||
maybe_close_file(buffer.file)
|
||||
GenServer.reply buffer.from, {:error, reason}
|
||||
GenServer.reply(buffer.from, {:error, reason})
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_open_file(opts) do
|
||||
{file, opts} = Keyword.pop(opts, :file)
|
||||
|
||||
case file do
|
||||
filename when is_binary(filename) ->
|
||||
# debug_log "Opening file: #{filename}"
|
||||
File.rm(file)
|
||||
:ok = File.touch(filename)
|
||||
:ok = File.touch(filename)
|
||||
{:ok, fd} = :file.open(filename, [:write, :raw])
|
||||
{fd, Keyword.merge(opts, [stream_to: self(), async: :once])}
|
||||
_ -> {nil, opts}
|
||||
{fd, Keyword.merge(opts, stream_to: self(), async: :once)}
|
||||
|
||||
_ ->
|
||||
{nil, opts}
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_stream_to_file(nil, _, _data), do: :ok
|
||||
defp maybe_stream_to_file(_, code, _data) when code in @redirect_status_codes, do: :ok
|
||||
defp maybe_stream_to_file(fd, _code, data) when is_binary(data) do
|
||||
defp maybe_stream_to_file(nil, _, _data), do: :ok
|
||||
defp maybe_stream_to_file(_, code, _data) when code in @redirect_status_codes, do: :ok
|
||||
|
||||
defp maybe_stream_to_file(fd, _code, data) when is_binary(data) do
|
||||
# debug_log "[#{inspect self()}] writing data to file."
|
||||
:ok = :file.write(fd, data)
|
||||
end
|
||||
|
@ -260,37 +319,45 @@ defmodule Farmbot.HTTP do
|
|||
defp maybe_close_file(fd), do: :file.close(fd)
|
||||
|
||||
defp maybe_log_progress(%Buffer{file: file, progress_callback: pcb})
|
||||
when is_nil(file) or is_nil(pcb) do
|
||||
when is_nil(file) or is_nil(pcb) do
|
||||
# debug_log "File (#{inspect file}) or progress callback: #{inspect pcb} are nil"
|
||||
:ok
|
||||
end
|
||||
|
||||
defp maybe_log_progress(%Buffer{file: _file, file_size: fs} = buffer) do
|
||||
downloaded_bytes = byte_size(buffer.data)
|
||||
|
||||
case fs do
|
||||
numstr when is_binary(numstr) ->
|
||||
total_bytes = numstr |> String.to_integer()
|
||||
buffer.progress_callback.(downloaded_bytes, total_bytes)
|
||||
other when (other in [:complete]) or is_nil(other) ->
|
||||
|
||||
other when other in [:complete] or is_nil(other) ->
|
||||
buffer.progress_callback.(downloaded_bytes, other)
|
||||
end
|
||||
end
|
||||
|
||||
defp do_api_request({method, url, body, headers, opts, from}, %{token: tkn} = state) do
|
||||
headers = headers
|
||||
|> add_header({"Authorization", "Bearer " <> tkn})
|
||||
|> add_header({"Content-Type", "application/json"})
|
||||
headers =
|
||||
headers
|
||||
|> add_header({"Authorization", "Bearer " <> tkn})
|
||||
|> add_header({"Content-Type", "application/json"})
|
||||
|
||||
opts = opts |> Keyword.put(:timeout, :infinity)
|
||||
#TODO Fix this.
|
||||
# TODO Fix this.
|
||||
url = "https:" <> Farmbot.Jwt.decode!(tkn).iss <> url
|
||||
do_normal_request({method, url, body, headers, opts, from}, nil, state)
|
||||
end
|
||||
|
||||
defp do_normal_request({method, url, body, headers, opts, from}, file, state) do
|
||||
case HTTPoison.request(method, url, body, headers, opts) do
|
||||
{:ok, %HTTPoison.Response{status_code: code, headers: resp_headers}} when code in @redirect_status_codes ->
|
||||
redir = Enum.find_value(resp_headers, fn({header, val}) -> if header == "Location", do: val, else: false end)
|
||||
{:ok, %HTTPoison.Response{status_code: code, headers: resp_headers}}
|
||||
when code in @redirect_status_codes ->
|
||||
redir =
|
||||
Enum.find_value(resp_headers, fn {header, val} ->
|
||||
if header == "Location", do: val, else: false
|
||||
end)
|
||||
|
||||
if redir do
|
||||
# debug_log "redirect"
|
||||
do_normal_request({method, redir, body, headers, opts, from}, file, state)
|
||||
|
@ -299,67 +366,80 @@ defmodule Farmbot.HTTP do
|
|||
GenServer.reply(from, {:error, :no_server_for_redirect})
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
{:ok, %HTTPoison.Response{body: body, headers: headers, status_code: code}} ->
|
||||
GenServer.reply(from, {:ok, %Response{body: body, headers: headers, status_code: code}})
|
||||
{:noreply, state}
|
||||
|
||||
{:ok, %AsyncResponse{id: ref}} ->
|
||||
timeout = Process.send_after(self(), {:timeout, ref}, 30_000)
|
||||
timeout = Process.send_after(self(), {:timeout, ref}, 30000)
|
||||
|
||||
req = %Buffer{
|
||||
id: ref,
|
||||
from: from,
|
||||
timeout: timeout,
|
||||
file: file,
|
||||
data: "",
|
||||
headers: nil,
|
||||
id: ref,
|
||||
from: from,
|
||||
timeout: timeout,
|
||||
file: file,
|
||||
data: "",
|
||||
headers: nil,
|
||||
status_code: nil,
|
||||
progress_callback: Keyword.fetch!(opts, :progress_callback),
|
||||
request: {method, url, body, headers, opts},
|
||||
request: {method, url, body, headers, opts}
|
||||
}
|
||||
|
||||
{:noreply, %{state | requests: Map.put(state.requests, ref, req)}}
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
GenServer.reply from, {:error, reason}
|
||||
GenServer.reply(from, {:error, reason})
|
||||
{:noreply, state}
|
||||
{:error, reason} ->
|
||||
GenServer.reply from, {:error, reason}
|
||||
|
||||
{:error, reason} ->
|
||||
GenServer.reply(from, {:error, reason})
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_redirect_request(%Buffer{} = buffer, redir, state) do
|
||||
{method, _url, body, headers, opts} = buffer.request
|
||||
|
||||
case HTTPoison.request(method, redir, body, headers, opts) do
|
||||
{:ok, %AsyncResponse{id: ref}} ->
|
||||
req = %Buffer{ buffer |
|
||||
id: ref,
|
||||
from: buffer.from,
|
||||
file: buffer.file,
|
||||
data: "",
|
||||
headers: nil,
|
||||
status_code: nil,
|
||||
request: {method, redir, body, headers, opts},
|
||||
req = %Buffer{
|
||||
buffer
|
||||
| id: ref,
|
||||
from: buffer.from,
|
||||
file: buffer.file,
|
||||
data: "",
|
||||
headers: nil,
|
||||
status_code: nil,
|
||||
request: {method, redir, body, headers, opts}
|
||||
}
|
||||
|
||||
state = %{state | requests: Map.delete(state.requests, buffer.id)}
|
||||
state = %{state | requests: Map.put(state.requests, ref, req)}
|
||||
{:noreply, state}
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
GenServer.reply buffer.from, {:error, reason}
|
||||
GenServer.reply(buffer.from, {:error, reason})
|
||||
{:noreply, state}
|
||||
{:error, reason} ->
|
||||
GenServer.reply buffer.from, {:error, reason}
|
||||
|
||||
{:error, reason} ->
|
||||
GenServer.reply(buffer.from, {:error, reason})
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
defp finish_request(%Buffer{status_code: status_code} = buffer, state) when status_code in @redirect_status_codes do
|
||||
defp finish_request(%Buffer{status_code: status_code} = buffer, state)
|
||||
when status_code in @redirect_status_codes do
|
||||
# debug_log "#{inspect Tuple.delete_at(buffer.from, 0)} Trying to redirect: (#{status_code})"
|
||||
redir = Enum.find_value(buffer.headers,
|
||||
fn({header, val}) ->
|
||||
redir =
|
||||
Enum.find_value(buffer.headers, fn {header, val} ->
|
||||
case header do
|
||||
"Location" -> val
|
||||
"location" -> val
|
||||
_ -> false
|
||||
end
|
||||
end)
|
||||
|
||||
if redir do
|
||||
do_redirect_request(buffer, redir, state)
|
||||
else
|
||||
|
@ -373,15 +453,18 @@ defmodule Farmbot.HTTP do
|
|||
# debug_log "Request finish."
|
||||
response = %Response{
|
||||
status_code: buffer.status_code,
|
||||
body: buffer.data,
|
||||
headers: buffer.headers
|
||||
body: buffer.data,
|
||||
headers: buffer.headers
|
||||
}
|
||||
|
||||
if buffer.timeout, do: Process.cancel_timer(buffer.timeout)
|
||||
maybe_close_file(buffer.file)
|
||||
|
||||
case buffer.file_size do
|
||||
nil -> maybe_log_progress(%{buffer | file_size: :complete})
|
||||
nil -> maybe_log_progress(%{buffer | file_size: :complete})
|
||||
_num -> maybe_log_progress(%{buffer | file_size: "#{byte_size(buffer.data)}"})
|
||||
end
|
||||
|
||||
GenServer.reply(buffer.from, {:ok, response})
|
||||
{:noreply, %{state | requests: Map.delete(state.requests, buffer.id)}}
|
||||
end
|
||||
|
@ -393,15 +476,16 @@ defmodule Farmbot.HTTP do
|
|||
defp add_header(headers, new), do: [new | headers]
|
||||
|
||||
defp fb_opts(opts) do
|
||||
Keyword.merge(opts, [
|
||||
ssl: [{:versions, [:'tlsv1.2']}],
|
||||
hackney: [:insecure, pool: :farmbot_http_pool],
|
||||
recv_timeout: :infinity,
|
||||
timeout: :infinity,
|
||||
# stream_to: self(),
|
||||
# follow_redirect: false,
|
||||
# async: :once
|
||||
])
|
||||
end
|
||||
Keyword.merge(
|
||||
opts,
|
||||
ssl: [{:versions, [:"tlsv1.2"]}],
|
||||
hackney: [:insecure, pool: :farmbot_http_pool],
|
||||
recv_timeout: :infinity,
|
||||
timeout: :infinity
|
||||
)
|
||||
|
||||
# stream_to: self(),
|
||||
# follow_redirect: false,
|
||||
# async: :once
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,8 +15,8 @@ defmodule Farmbot.HTTP.ImageUploader do
|
|||
end
|
||||
|
||||
def init(http) do
|
||||
File.rm_rf! @images_path
|
||||
File.mkdir_p! @images_path
|
||||
File.rm_rf!(@images_path)
|
||||
File.mkdir_p!(@images_path)
|
||||
:fs_app.start(:normal, [])
|
||||
:fs.subscribe()
|
||||
Process.flag(:trap_exit, true)
|
||||
|
@ -26,9 +26,9 @@ defmodule Farmbot.HTTP.ImageUploader do
|
|||
def handle_info({_pid, {:fs, :file_event}, {path, [:modified, :closed]}}, state) do
|
||||
if matches_any_pattern?(path, [~r{/tmp/images/.*(jpg|jpeg|png|gif)}]) do
|
||||
[x, y, z] = [-9000, -9000, -9000]
|
||||
Logger.warn "FIX THIS"
|
||||
Logger.warn("FIX THIS")
|
||||
meta = %{x: x, y: y, z: z}
|
||||
pid = spawn __MODULE__, :upload, [state.http, path, meta]
|
||||
pid = spawn(__MODULE__, :upload, [state.http, path, meta])
|
||||
{:noreply, %{state | uploads: Map.put(state.uploads, pid, {path, meta, 0})}}
|
||||
else
|
||||
{:noreply, state}
|
||||
|
@ -37,18 +37,24 @@ defmodule Farmbot.HTTP.ImageUploader do
|
|||
|
||||
def handle_info({:EXIT, pid, reason}, state) do
|
||||
case state.uploads[pid] do
|
||||
nil -> {:noreply, state}
|
||||
nil ->
|
||||
{:noreply, state}
|
||||
|
||||
{path, _meta, 6 = ret} ->
|
||||
Logger.error ">> failed to upload #{path} #{ret} times. Giving up."
|
||||
File.rm path
|
||||
Logger.error(">> failed to upload #{path} #{ret} times. Giving up.")
|
||||
File.rm(path)
|
||||
{:noreply, %{state | uploads: Map.delete(state.uploads, pid)}}
|
||||
{path, meta, retries} ->
|
||||
Logger.warn ">> faile to upload #{path} #{inspect reason}. Going to retry."
|
||||
|
||||
{path, meta, retries} ->
|
||||
Logger.warn(">> faile to upload #{path} #{inspect(reason)}. Going to retry.")
|
||||
Process.sleep(1000 * retries)
|
||||
new_pid = spawn __MODULE__, :upload, [state.http, path, meta]
|
||||
new_uploads = state
|
||||
new_pid = spawn(__MODULE__, :upload, [state.http, path, meta])
|
||||
|
||||
new_uploads =
|
||||
state
|
||||
|> Map.delete(pid)
|
||||
|> Map.put(new_pid, {path, meta, retries + 1})
|
||||
|
||||
{:noreply, %{state | uploads: new_uploads}}
|
||||
end
|
||||
end
|
||||
|
@ -61,15 +67,16 @@ defmodule Farmbot.HTTP.ImageUploader do
|
|||
# /lib/phoenix_live_reload/channel.ex#L27
|
||||
defp matches_any_pattern?(path, patterns) do
|
||||
path = to_string(path)
|
||||
|
||||
Enum.any?(patterns, fn pattern ->
|
||||
String.match?(path, pattern)
|
||||
end)
|
||||
end
|
||||
|
||||
def upload(http, file_path, meta) do
|
||||
Logger.info "Image Watcher trying to upload #{file_path}", type: :busy
|
||||
Logger.info("Image Watcher trying to upload #{file_path}", type: :busy)
|
||||
Farmbot.HTTP.upload_file!(http, file_path, meta)
|
||||
File.rm!(file_path)
|
||||
Logger.info "Image Watcher uploaded #{file_path}", type: :success
|
||||
Logger.info("Image Watcher uploaded #{file_path}", type: :success)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,9 +4,8 @@ defmodule Farmbot.HTTP.Response do
|
|||
|
||||
@typedoc "HTTP Response"
|
||||
@type t :: %__MODULE__{
|
||||
body: Farmbot.Behaviour.HTTP.body,
|
||||
headers: Farmbot.Behaviour.HTTP.headers,
|
||||
status_code: Farmbot.Behaviour.HTTP.status_code
|
||||
}
|
||||
|
||||
body: Farmbot.Behaviour.HTTP.body(),
|
||||
headers: Farmbot.Behaviour.HTTP.headers(),
|
||||
status_code: Farmbot.Behaviour.HTTP.status_code()
|
||||
}
|
||||
end
|
||||
|
|
|
@ -12,8 +12,8 @@ defmodule Farmbot.HTTP.Supervisor do
|
|||
children = [
|
||||
worker(Farmbot.HTTP, [token, [name: Farmbot.HTTP]])
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_all]
|
||||
supervise(children, opts)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -6,24 +6,25 @@ defmodule Farmbot.Jwt do
|
|||
:exp,
|
||||
:iss,
|
||||
:mqtt,
|
||||
:os_update_server,
|
||||
:os_update_server
|
||||
]
|
||||
|
||||
@typedoc "Type def for Farmbot Web Token."
|
||||
@type t :: %__MODULE__{
|
||||
bot: binary,
|
||||
exp: number,
|
||||
iss: binary,
|
||||
mqtt: binary,
|
||||
os_update_server: binary,
|
||||
}
|
||||
bot: binary,
|
||||
exp: number,
|
||||
iss: binary,
|
||||
mqtt: binary,
|
||||
os_update_server: binary
|
||||
}
|
||||
|
||||
@doc "Decode a token."
|
||||
@spec decode(binary) :: {:ok, t} | {:error, term}
|
||||
def decode(tkn) do
|
||||
body = tkn |> String.split(".") |> Enum.at(1)
|
||||
|
||||
with {:ok, json} <- Base.decode64(body, padding: false),
|
||||
{:ok, jwt} <- Poison.decode(json, as: %__MODULE__{}),
|
||||
{:ok, jwt} <- Poison.decode(json, as: %__MODULE__{}),
|
||||
do: {:ok, jwt}
|
||||
end
|
||||
|
||||
|
|
|
@ -7,11 +7,20 @@ defmodule Farmbot.Lib.Helpers do
|
|||
Helper for checking if a binary is a uuid.
|
||||
"""
|
||||
def uuid?(uuid) do
|
||||
match?(<<_::size(64), <<45>>,
|
||||
_::size(32), <<45>>,
|
||||
_::size(32), <<45>>,
|
||||
_::size(32), <<45>>,
|
||||
_::size(96)>>, uuid)
|
||||
match?(
|
||||
<<
|
||||
_::size(64),
|
||||
<<45>>,
|
||||
_::size(32),
|
||||
<<45>>,
|
||||
_::size(32),
|
||||
<<45>>,
|
||||
_::size(32),
|
||||
<<45>>,
|
||||
_::size(96)
|
||||
>>,
|
||||
uuid
|
||||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -22,5 +31,4 @@ defmodule Farmbot.Lib.Helpers do
|
|||
byte_size(unquote(uuid)) == 36
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -5,11 +5,9 @@ end
|
|||
defmodule Farmbot.Log do
|
||||
@moduledoc "Farmbot Log Object."
|
||||
alias Farmbot.Log.Meta
|
||||
|
||||
defstruct [
|
||||
meta: %Meta{x: -1, y: -1, z: -1},
|
||||
message: nil,
|
||||
created_at: nil,
|
||||
channels: []
|
||||
]
|
||||
|
||||
defstruct meta: %Meta{x: -1, y: -1, z: -1},
|
||||
message: nil,
|
||||
created_at: nil,
|
||||
channels: []
|
||||
end
|
||||
|
|
|
@ -10,7 +10,12 @@ defmodule Farmbot.Logger do
|
|||
|
||||
def init([]) do
|
||||
Logger.add_backend(Logger.Backends.Farmbot, [])
|
||||
{:producer_consumer, %{meta: %Log.Meta{x: -1, y: -1, z: -1}}, subscribe_to: [Farmbot.Firmware]}
|
||||
|
||||
{
|
||||
:producer_consumer,
|
||||
%{meta: %Log.Meta{x: -1, y: -1, z: -1}},
|
||||
subscribe_to: [Farmbot.Firmware]
|
||||
}
|
||||
end
|
||||
|
||||
def handle_demand(_, state) do
|
||||
|
@ -18,12 +23,14 @@ defmodule Farmbot.Logger do
|
|||
end
|
||||
|
||||
def handle_events(gcodes, _from, state) do
|
||||
{x, y, z} = Enum.find_value(gcodes, fn(code) ->
|
||||
case code do
|
||||
{:report_current_position, x, y, z} -> {x, y, z}
|
||||
_ -> false
|
||||
end
|
||||
end)
|
||||
{x, y, z} =
|
||||
Enum.find_value(gcodes, fn code ->
|
||||
case code do
|
||||
{:report_current_position, x, y, z} -> {x, y, z}
|
||||
_ -> false
|
||||
end
|
||||
end)
|
||||
|
||||
{:noreply, [], %{state | meta: %{state.meta | x: x, y: y, z: z}}}
|
||||
end
|
||||
|
||||
|
|
|
@ -11,17 +11,25 @@ defmodule Farmbot.Repo.FarmEvent do
|
|||
import Ecto.Changeset
|
||||
|
||||
schema "farm_events" do
|
||||
field :start_time, :utc_datetime
|
||||
field :end_time, :utc_datetime
|
||||
field :repeat, :integer
|
||||
field :time_unit, :string
|
||||
field :executable_type, Farmbot.Repo.ModuleType.FarmEvent
|
||||
field :executable_id, :integer
|
||||
field(:start_time, :utc_datetime)
|
||||
field(:end_time, :utc_datetime)
|
||||
field(:repeat, :integer)
|
||||
field(:time_unit, :string)
|
||||
field(:executable_type, Farmbot.Repo.ModuleType.FarmEvent)
|
||||
field(:executable_id, :integer)
|
||||
end
|
||||
|
||||
use Farmbot.Repo.Syncable
|
||||
@required_fields [:id, :start_time, :end_time, :repeat,
|
||||
:time_unit, :executable_type, :executable_id]
|
||||
|
||||
@required_fields [
|
||||
:id,
|
||||
:start_time,
|
||||
:end_time,
|
||||
:repeat,
|
||||
:time_unit,
|
||||
:executable_type,
|
||||
:executable_id
|
||||
]
|
||||
|
||||
def changeset(farm_event, params \\ %{}) do
|
||||
farm_event
|
||||
|
@ -30,5 +38,4 @@ defmodule Farmbot.Repo.FarmEvent do
|
|||
|> validate_required(@required_fields)
|
||||
|> unique_constraint(:id)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -5,11 +5,14 @@ defmodule Farmbot.Repo.ModuleType do
|
|||
|
||||
defmacro __using__(opts) do
|
||||
mods = Keyword.fetch!(opts, :valid_mods)
|
||||
|
||||
quote do
|
||||
@valid_short_strs unquote(mods)
|
||||
@valid_mods Enum.map(unquote(mods), fn(mod) -> Module.concat([Farmbot, Repo, mod]) end)
|
||||
@valid_mods Enum.map(unquote(mods), fn mod -> Module.concat([Farmbot, Repo, mod]) end)
|
||||
|
||||
@moduledoc "Custom Ecto.Type for changing a string field to one of #{inspect @valid_short_strs}"
|
||||
@moduledoc "Custom Ecto.Type for changing a string field to one of #{
|
||||
inspect(@valid_short_strs)
|
||||
}"
|
||||
@behaviour Ecto.Type
|
||||
require Logger
|
||||
|
||||
|
@ -21,8 +24,8 @@ defmodule Farmbot.Repo.ModuleType do
|
|||
if match?(<<"Elixir.", _::binary>>, to_string(module)) do
|
||||
module
|
||||
|> Module.split()
|
||||
|> List.last
|
||||
|> fn(mod) -> {:ok, mod} end.()
|
||||
|> List.last()
|
||||
|> (fn mod -> {:ok, mod} end).()
|
||||
else
|
||||
:error
|
||||
end
|
||||
|
@ -47,10 +50,9 @@ defmodule Farmbot.Repo.ModuleType do
|
|||
end
|
||||
|
||||
def dump(fail) do
|
||||
Logger.error "failed to load #{inspect fail}"
|
||||
Logger.error("failed to load #{inspect(fail)}")
|
||||
:error
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
defmodule Farmbot.Repo.ModuleType.FarmEvent do
|
||||
use Farmbot.Repo.ModuleType, valid_mods: ~w(Sequence Regimen)
|
||||
use Farmbot.Repo.ModuleType, valid_mods: ~w(Sequence Regimen)
|
||||
end
|
||||
|
|
|
@ -7,9 +7,9 @@ defmodule Farmbot.Repo.Peripheral do
|
|||
import Ecto.Changeset
|
||||
|
||||
schema "peripherals" do
|
||||
field :pin, :integer
|
||||
field :mode, :integer
|
||||
field :label, :string
|
||||
field(:pin, :integer)
|
||||
field(:mode, :integer)
|
||||
field(:label, :string)
|
||||
end
|
||||
|
||||
use Farmbot.Repo.Syncable
|
||||
|
|
|
@ -5,12 +5,12 @@ defmodule Farmbot.Repo.Point do
|
|||
import Ecto.Changeset
|
||||
|
||||
schema "points" do
|
||||
field :name, :string
|
||||
field :x, Farmbot.Repo.JSONFloatType
|
||||
field :y, Farmbot.Repo.JSONFloatType
|
||||
field :z, Farmbot.Repo.JSONFloatType
|
||||
field :meta, Farmbot.Repo.JSONType
|
||||
field :pointer_type, Farmbot.Repo.ModuleType.Point
|
||||
field(:name, :string)
|
||||
field(:x, Farmbot.Repo.JSONFloatType)
|
||||
field(:y, Farmbot.Repo.JSONFloatType)
|
||||
field(:z, Farmbot.Repo.JSONFloatType)
|
||||
field(:meta, Farmbot.Repo.JSONType)
|
||||
field(:pointer_type, Farmbot.Repo.ModuleType.Point)
|
||||
end
|
||||
|
||||
use Farmbot.Repo.Syncable
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Farmbot.Repo.Regimen do
|
|||
import Ecto.Changeset
|
||||
|
||||
schema "regimens" do
|
||||
field :name, :string
|
||||
field(:name, :string)
|
||||
end
|
||||
|
||||
use Farmbot.Repo.Syncable
|
||||
|
|
|
@ -30,42 +30,57 @@ defmodule Farmbot.Repo do
|
|||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
|
||||
|
||||
@moduledoc "Storage for Farmbot Resources."
|
||||
use Ecto.Repo, otp_app: :farmbot, adapter: Sqlite.Ecto2
|
||||
|
||||
alias Farmbot.Repo.{
|
||||
FarmEvent, GenericPointer, Peripheral,
|
||||
Point, Regimen, Sequence, ToolSlot, Tool
|
||||
}
|
||||
FarmEvent,
|
||||
GenericPointer,
|
||||
Peripheral,
|
||||
Point,
|
||||
Regimen,
|
||||
Sequence,
|
||||
ToolSlot,
|
||||
Tool
|
||||
}
|
||||
|
||||
@default_syncables [FarmEvent, GenericPointer, Peripheral,
|
||||
Point, Regimen, Sequence, ToolSlot, Tool]
|
||||
@default_syncables [
|
||||
FarmEvent,
|
||||
GenericPointer,
|
||||
Peripheral,
|
||||
Point,
|
||||
Regimen,
|
||||
Sequence,
|
||||
ToolSlot,
|
||||
Tool
|
||||
]
|
||||
|
||||
@doc "A list of all the resources."
|
||||
def syncables, do: Application.get_env(:farmbot, :repo)[:farmbot_syncables] || @default_syncables
|
||||
def syncables,
|
||||
do: Application.get_env(:farmbot, :repo)[:farmbot_syncables] || @default_syncables
|
||||
|
||||
@doc "Sync all the modules that export a `sync/1` function."
|
||||
def sync!(http \\ Farmbot.HTTP) do
|
||||
for syncable <- syncables() do
|
||||
if Code.ensure_loaded?(syncable) and function_exported?(syncable, :sync!, 2) do
|
||||
spawn fn() ->
|
||||
spawn(fn ->
|
||||
syncable.sync!(__MODULE__, http)
|
||||
end
|
||||
end)
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
repos = [Farmbot.Repo.A, Farmbot.Repo.B]
|
||||
|
||||
for repo <- repos do
|
||||
defmodule repo do
|
||||
use Farmbot.Repo
|
||||
|
|
|
@ -14,16 +14,18 @@ defmodule Farmbot.Repo.Sequence do
|
|||
# Cast and Dump will just be forwareded to the JSONType module.
|
||||
defdelegate cast(data), to: JSONType
|
||||
defdelegate dump(data), to: JSONType
|
||||
defdelegate type, to: JSONType
|
||||
defdelegate type, to: JSONType
|
||||
|
||||
def load(text) do
|
||||
{:ok, data} = text |> JSONType.load()
|
||||
res = Enum.map(data, fn(data) ->
|
||||
Farmbot.CeleryScript.Ast.parse(data)
|
||||
end)
|
||||
|
||||
res =
|
||||
Enum.map(data, fn data ->
|
||||
Farmbot.CeleryScript.Ast.parse(data)
|
||||
end)
|
||||
|
||||
{:ok, res}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
defmodule CeleryScriptArgs do
|
||||
|
@ -33,21 +35,20 @@ defmodule Farmbot.Repo.Sequence do
|
|||
# Cast and Dump will just be forwareded to the JSONType module.
|
||||
defdelegate cast(data), to: JSONType
|
||||
defdelegate dump(data), to: JSONType
|
||||
defdelegate type, to: JSONType
|
||||
defdelegate type, to: JSONType
|
||||
|
||||
def load(text) do
|
||||
{:ok, data} = text |> JSONType.load()
|
||||
res = Farmbot.CeleryScript.Ast.parse_args(data)
|
||||
{:ok, res}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
schema "sequences" do
|
||||
field :name, :string
|
||||
field :kind, :string
|
||||
field :args, CeleryScriptArgs
|
||||
field :body, CeleryScriptBody
|
||||
field(:name, :string)
|
||||
field(:kind, :string)
|
||||
field(:args, CeleryScriptArgs)
|
||||
field(:body, CeleryScriptBody)
|
||||
end
|
||||
|
||||
use Farmbot.Repo.Syncable
|
||||
|
|
|
@ -4,15 +4,16 @@ defmodule Farmbot.Repo.Supervisor do
|
|||
@repos [Farmbot.Repo.A, Farmbot.Repo.B]
|
||||
|
||||
use Supervisor
|
||||
|
||||
def start_link(token, opts \\ []) do
|
||||
Supervisor.start_link(__MODULE__, token, opts)
|
||||
end
|
||||
|
||||
def init(_token) do
|
||||
@repos
|
||||
|> Enum.map(fn(repo) ->
|
||||
supervisor(repo, [])
|
||||
end)
|
||||
|> Enum.map(fn repo ->
|
||||
supervisor(repo, [])
|
||||
end)
|
||||
|> Kernel.++([worker(Farmbot.Repo, [@repos, [name: Farmbot.Repo]])])
|
||||
|> supervise(strategy: :one_for_one)
|
||||
end
|
||||
|
|
|
@ -2,13 +2,15 @@ defmodule Farmbot.Repo.Syncable do
|
|||
@moduledoc "Behaviour for syncable modules."
|
||||
|
||||
@doc "Sync this module."
|
||||
@callback sync!(module, GenServer.server) :: any | no_return
|
||||
@callback sync!(module, GenServer.server()) :: any | no_return
|
||||
@optional_callbacks [sync!: 2]
|
||||
|
||||
@doc "Changes iso8601 times to DateTime structs."
|
||||
def ensure_time(struct, []), do: struct
|
||||
|
||||
def ensure_time(struct, [field | rest]) do
|
||||
{:ok, dt, _} = DateTime.from_iso8601(Map.get(struct, field))
|
||||
|
||||
%{struct | field => dt}
|
||||
|> ensure_time(rest)
|
||||
end
|
||||
|
@ -16,13 +18,13 @@ defmodule Farmbot.Repo.Syncable do
|
|||
@doc false
|
||||
defmacro __using__(opts) do
|
||||
enable_sync = Keyword.get(opts, :sync, true)
|
||||
|
||||
quote do
|
||||
@behaviour Farmbot.Repo.Syncable
|
||||
import Farmbot.Repo.Syncable, only: [ensure_time: 2]
|
||||
require Logger
|
||||
|
||||
if unquote(enable_sync) do
|
||||
|
||||
@doc """
|
||||
Syncs all #{__MODULE__ |> Module.split() |> List.last()}'s from the Farmbot Web App.
|
||||
1) Fetches JSON from the API.
|
||||
|
@ -33,35 +35,38 @@ defmodule Farmbot.Repo.Syncable do
|
|||
def sync!(repo, http) do
|
||||
{_, source} = struct(__MODULE__).__meta__.source
|
||||
color = Farmbot.DebugLog.color(:RANDOM)
|
||||
Logger.info "#{color}[#{source}] Begin sync."
|
||||
Logger.info("#{color}[#{source}] Begin sync.")
|
||||
# |> fn(bin) -> IO.inspect(Poison.decode!(bin)); bin end.()
|
||||
# |> fn(obj) -> IO.inspect(obj); obj end.()
|
||||
http
|
||||
|> Farmbot.HTTP.get!("/api/#{source}")
|
||||
|> Map.fetch!(:body)
|
||||
# |> fn(bin) -> IO.inspect(Poison.decode!(bin)); bin end.()
|
||||
|> Poison.decode!(as: [%__MODULE__{}])
|
||||
# |> fn(obj) -> IO.inspect(obj); obj end.()
|
||||
|> Enum.each(fn(obj) ->
|
||||
# We need to check if this object exists in the database.
|
||||
case repo.get(__MODULE__, obj.id) do
|
||||
# If it does not, just return the newly created object.
|
||||
nil -> obj
|
||||
# if there is an existing record, copy the ecto meta from the old
|
||||
# record. This allows `insert_or_update` to work properly.
|
||||
existing -> %{obj | __meta__: existing.__meta__}
|
||||
end
|
||||
|> __MODULE__.changeset() |> repo.insert_or_update!()
|
||||
end)
|
||||
Logger.info "#{color}[#{source}] Complete sync."
|
||||
end
|
||||
|> Enum.each(fn obj ->
|
||||
# We need to check if this object exists in the database.
|
||||
case repo.get(__MODULE__, obj.id) do
|
||||
# If it does not, just return the newly created object.
|
||||
nil ->
|
||||
obj
|
||||
|
||||
# if there is an existing record, copy the ecto meta from the old
|
||||
# record. This allows `insert_or_update` to work properly.
|
||||
existing ->
|
||||
%{obj | __meta__: existing.__meta__}
|
||||
end
|
||||
|> __MODULE__.changeset()
|
||||
|> repo.insert_or_update!()
|
||||
end)
|
||||
|
||||
Logger.info("#{color}[#{source}] Complete sync.")
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Fetch all #{__MODULE__}'s from the Repo."
|
||||
def fetch_all(repo) do
|
||||
import Ecto.Query
|
||||
(from i in __MODULE__, select: i) |> repo.all()
|
||||
from(i in __MODULE__, select: i) |> repo.all()
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ defmodule Farmbot.Repo.Tool do
|
|||
import Ecto.Changeset
|
||||
|
||||
schema "tools" do
|
||||
field :name, :string
|
||||
field(:name, :string)
|
||||
end
|
||||
|
||||
use Farmbot.Repo.Syncable
|
||||
|
|
|
@ -5,10 +5,10 @@ defmodule Farmbot.Repo.ToolSlot do
|
|||
import Ecto.Changeset
|
||||
|
||||
schema "tool_slots" do
|
||||
field :tool_id, :integer
|
||||
field(:tool_id, :integer)
|
||||
end
|
||||
|
||||
use Farmbot.Repo.Syncable, sync: :false
|
||||
use Farmbot.Repo.Syncable, sync: false
|
||||
@required_fields [:id, :tool_id]
|
||||
|
||||
def changeset(farm_event, params \\ %{}) do
|
||||
|
|
|
@ -6,20 +6,33 @@ defmodule Farmbot.System.ConfigStorage do
|
|||
|
||||
@doc "Please be careful with this. It uses a lot of queries."
|
||||
def get_config_as_map do
|
||||
groups = (from g in Group, select: g) |> all()
|
||||
Map.new(groups, fn(group) ->
|
||||
vals = (from b in Config, where: b.group_id == ^group.id, select: b) |> all()
|
||||
s = Map.new(vals, fn(val) ->
|
||||
[value] = Enum.find_value(val |> Map.from_struct, fn({_key, _val} = f) ->
|
||||
case f do
|
||||
{:bool_value_id, id} when is_number(id) -> all(from v in BoolValue, where: v.id == ^id, select: v.value)
|
||||
{:float_value_id, id} when is_number(id) -> all(from v in FloatValue, where: v.id == ^id, select: v.value)
|
||||
{:string_value_id, id} when is_number(id) -> all(from v in StringValue, where: v.id == ^id, select: v.value)
|
||||
_ -> false
|
||||
end
|
||||
groups = from(g in Group, select: g) |> all()
|
||||
|
||||
Map.new(groups, fn group ->
|
||||
vals = from(b in Config, where: b.group_id == ^group.id, select: b) |> all()
|
||||
|
||||
s =
|
||||
Map.new(vals, fn val ->
|
||||
[value] =
|
||||
Enum.find_value(val |> Map.from_struct(), fn {_key, _val} = f ->
|
||||
case f do
|
||||
{:bool_value_id, id} when is_number(id) ->
|
||||
all(from(v in BoolValue, where: v.id == ^id, select: v.value))
|
||||
|
||||
{:float_value_id, id} when is_number(id) ->
|
||||
all(from(v in FloatValue, where: v.id == ^id, select: v.value))
|
||||
|
||||
{:string_value_id, id} when is_number(id) ->
|
||||
all(from(v in StringValue, where: v.id == ^id, select: v.value))
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end)
|
||||
|
||||
{val.key, value}
|
||||
end)
|
||||
{val.key, value}
|
||||
end)
|
||||
|
||||
{group.group_name, s}
|
||||
end)
|
||||
end
|
||||
|
@ -47,10 +60,17 @@ defmodule Farmbot.System.ConfigStorage do
|
|||
|
||||
def get_bool_value(group_name, key_name) do
|
||||
group_id = get_group_id(group_name)
|
||||
case (from c in Config, where: c.group_id == ^group_id and c.key == ^key_name, select: c.bool_value_id) |> all() do
|
||||
|
||||
case from(
|
||||
c in Config,
|
||||
where: c.group_id == ^group_id and c.key == ^key_name,
|
||||
select: c.bool_value_id
|
||||
)
|
||||
|> all() do
|
||||
[type_id] ->
|
||||
[val] = (from v in BoolValue, where: v.id == ^type_id, select: v) |> all()
|
||||
[val] = from(v in BoolValue, where: v.id == ^type_id, select: v) |> all()
|
||||
val
|
||||
|
||||
[] ->
|
||||
raise "no such key #{key_name}"
|
||||
end
|
||||
|
@ -58,20 +78,36 @@ defmodule Farmbot.System.ConfigStorage do
|
|||
|
||||
def get_float_value(group_name, key_name) do
|
||||
group_id = get_group_id(group_name)
|
||||
[type_id] = (from c in Config, where: c.group_id == ^group_id and c.key == ^key_name, select: c.float_value_id) |> all()
|
||||
[val] = (from v in FloatValue, where: v.id == ^type_id, select: v) |> all()
|
||||
|
||||
[type_id] =
|
||||
from(
|
||||
c in Config,
|
||||
where: c.group_id == ^group_id and c.key == ^key_name,
|
||||
select: c.float_value_id
|
||||
)
|
||||
|> all()
|
||||
|
||||
[val] = from(v in FloatValue, where: v.id == ^type_id, select: v) |> all()
|
||||
val
|
||||
end
|
||||
|
||||
def get_string_value(group_name, key_name) do
|
||||
group_id = get_group_id(group_name)
|
||||
[type_id] = (from c in Config, where: c.group_id == ^group_id and c.key == ^key_name, select: c.string_value_id) |> all()
|
||||
[val] = (from v in StringValue, where: v.id == ^type_id, select: v) |> all()
|
||||
|
||||
[type_id] =
|
||||
from(
|
||||
c in Config,
|
||||
where: c.group_id == ^group_id and c.key == ^key_name,
|
||||
select: c.string_value_id
|
||||
)
|
||||
|> all()
|
||||
|
||||
[val] = from(v in StringValue, where: v.id == ^type_id, select: v) |> all()
|
||||
val
|
||||
end
|
||||
|
||||
defp get_group_id(group_name) do
|
||||
[group_id] = (from g in Group, where: g.group_name == ^group_name, select: g.id) |> all()
|
||||
[group_id] = from(g in Group, where: g.group_name == ^group_name, select: g.id) |> all()
|
||||
group_id
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ defmodule Farmbot.System.ConfigStorage.BoolValue do
|
|||
import Ecto.Changeset
|
||||
|
||||
schema "bool_values" do
|
||||
field :value, :boolean
|
||||
field(:value, :boolean)
|
||||
end
|
||||
|
||||
@required_fields []
|
||||
|
|
|
@ -6,11 +6,11 @@ defmodule Farmbot.System.ConfigStorage.Config do
|
|||
alias Farmbot.System.ConfigStorage.{Group, BoolValue, FloatValue, StringValue}
|
||||
|
||||
schema "configs" do
|
||||
belongs_to :group, Group
|
||||
belongs_to :string_value, StringValue
|
||||
belongs_to :bool_value, BoolValue
|
||||
belongs_to :float_value, FloatValue
|
||||
field :key, :string
|
||||
belongs_to(:group, Group)
|
||||
belongs_to(:string_value, StringValue)
|
||||
belongs_to(:bool_value, BoolValue)
|
||||
belongs_to(:float_value, FloatValue)
|
||||
field(:key, :string)
|
||||
end
|
||||
|
||||
@required_fields [:key, :group_id]
|
||||
|
|
|
@ -5,7 +5,7 @@ defmodule Farmbot.System.ConfigStorage.FloatValue do
|
|||
import Ecto.Changeset
|
||||
|
||||
schema "float_values" do
|
||||
field :value, :string
|
||||
field(:value, :string)
|
||||
end
|
||||
|
||||
@required_fields []
|
||||
|
|
|
@ -5,7 +5,7 @@ defmodule Farmbot.System.ConfigStorage.Group do
|
|||
import Ecto.Changeset
|
||||
|
||||
schema "groups" do
|
||||
field :group_name, :string
|
||||
field(:group_name, :string)
|
||||
end
|
||||
|
||||
@required_fields []
|
||||
|
|
|
@ -5,15 +5,15 @@ defmodule Farmbot.System.ConfigStorage.NetworkInterface do
|
|||
import Ecto.Changeset
|
||||
|
||||
schema "network_interfaces" do
|
||||
field :name, :string, null: false
|
||||
field :type, :string, null: false
|
||||
field(:name, :string, null: false)
|
||||
field(:type, :string, null: false)
|
||||
|
||||
## For wireless interfaces
|
||||
field :ssid, :string
|
||||
field :psk, :string
|
||||
field :security, :string
|
||||
field(:ssid, :string)
|
||||
field(:psk, :string)
|
||||
field(:security, :string)
|
||||
|
||||
field :ipv4_method, :string
|
||||
field(:ipv4_method, :string)
|
||||
end
|
||||
|
||||
@required_fields [:name, :type]
|
||||
|
|
|
@ -5,7 +5,7 @@ defmodule Farmbot.System.ConfigStorage.StringValue do
|
|||
import Ecto.Changeset
|
||||
|
||||
schema "string_values" do
|
||||
field :value, :string
|
||||
field(:value, :string)
|
||||
end
|
||||
|
||||
@required_fields []
|
||||
|
|
|
@ -2,15 +2,15 @@ defmodule Farmbot.System.Init do
|
|||
@moduledoc "Lets write init.d in Elixir!"
|
||||
|
||||
@doc "OTP Spec."
|
||||
@spec fb_init(atom, [term]) :: Supervisor.Spec.spec
|
||||
@spec fb_init(atom, [term]) :: Supervisor.Spec.spec()
|
||||
def fb_init(module, args) do
|
||||
import Supervisor.Spec
|
||||
supervisor(module, args, [restart: :permanent])
|
||||
supervisor(module, args, restart: :permanent)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Start an init module.
|
||||
returning {:error, reason} will factory reset the system.
|
||||
"""
|
||||
@callback start_link(term, Supervisor.options) :: Supervisor.supervisor
|
||||
@callback start_link(term, Supervisor.options()) :: Supervisor.supervisor()
|
||||
end
|
||||
|
|
|
@ -13,10 +13,10 @@ defmodule Farmbot.System.Init.Ecto do
|
|||
:ignore
|
||||
end
|
||||
|
||||
|
||||
@doc "Replacement for Mix.Tasks.Ecto.Create"
|
||||
def setup do
|
||||
repos = Application.get_env(:farmbot, :ecto_repos)
|
||||
|
||||
for repo <- repos do
|
||||
setup(repo)
|
||||
end
|
||||
|
@ -24,6 +24,7 @@ defmodule Farmbot.System.Init.Ecto do
|
|||
|
||||
def setup(repo) do
|
||||
db_file = Application.get_env(:farmbot, repo)[:database]
|
||||
|
||||
unless File.exists?(db_file) do
|
||||
:ok = repo.__adapter__.storage_up(repo.config)
|
||||
end
|
||||
|
@ -32,6 +33,7 @@ defmodule Farmbot.System.Init.Ecto do
|
|||
@doc "Replacement for Mix.Tasks.Ecto.Drop"
|
||||
def drop do
|
||||
repos = Application.get_env(:farmbot, :ecto_repos)
|
||||
|
||||
for repo <- repos do
|
||||
case drop(repo) do
|
||||
:ok -> :ok
|
||||
|
@ -48,6 +50,7 @@ defmodule Farmbot.System.Init.Ecto do
|
|||
@doc "Replacement for Mix.Tasks.Ecto.Migrate"
|
||||
def migrate do
|
||||
repos = Application.get_env(:farmbot, :ecto_repos)
|
||||
|
||||
for repo <- repos do
|
||||
# setup(repo)
|
||||
migrate(repo)
|
||||
|
@ -59,17 +62,24 @@ defmodule Farmbot.System.Init.Ecto do
|
|||
{:ok, pid, apps} = Mix.Ecto.ensure_started(repo, opts)
|
||||
|
||||
migrator = &Ecto.Migrator.run/4
|
||||
|
||||
migrations_path =
|
||||
(Application.get_env(:farmbot, repo)[:priv] || Path.join((:code.priv_dir(:farmbot) |> to_string), Module.split(repo) |> List.last() |> Macro.underscore()))
|
||||
(Application.get_env(:farmbot, repo)[:priv] ||
|
||||
Path.join(
|
||||
:code.priv_dir(:farmbot) |> to_string,
|
||||
Module.split(repo) |> List.last() |> Macro.underscore()
|
||||
))
|
||||
|> Kernel.<>("/migrations")
|
||||
|
||||
pool = repo.config[:pool]
|
||||
migrated =
|
||||
if function_exported?(pool, :unboxed_run, 2) do
|
||||
pool.unboxed_run(repo, fn -> migrator.(repo, migrations_path, :up, opts) end)
|
||||
else
|
||||
migrator.(repo, migrations_path, :up, opts)
|
||||
end
|
||||
pool = repo.config[:pool]
|
||||
|
||||
migrated =
|
||||
if function_exported?(pool, :unboxed_run, 2) do
|
||||
pool.unboxed_run(repo, fn -> migrator.(repo, migrations_path, :up, opts) end)
|
||||
else
|
||||
migrator.(repo, migrations_path, :up, opts)
|
||||
end
|
||||
|
||||
pid && repo.stop(pid)
|
||||
Mix.Ecto.restart_apps_if_migrated(apps, migrated)
|
||||
Process.sleep(500)
|
||||
|
|
|
@ -2,7 +2,7 @@ defmodule Farmbot.System.Init.FSCheckup do
|
|||
@moduledoc "Init module for bringup and teardown of ecto."
|
||||
use Supervisor
|
||||
@behaviour Farmbot.System.Init
|
||||
@data_path Application.get_env(:farmbot, :data_path) || Mix.raise "Unconfigured data path."
|
||||
@data_path Application.get_env(:farmbot, :data_path) || Mix.raise("Unconfigured data path.")
|
||||
require Logger
|
||||
|
||||
@doc "This will run migrations on all Farmbot Repos."
|
||||
|
@ -17,16 +17,20 @@ defmodule Farmbot.System.Init.FSCheckup do
|
|||
|
||||
defp do_checkup do
|
||||
check_file = Path.join(@data_path, "boot")
|
||||
|
||||
unless File.exists?(@data_path) do
|
||||
File.mkdir(@data_path)
|
||||
end
|
||||
Logger.info "Checking #{check_file}"
|
||||
|
||||
Logger.info("Checking #{check_file}")
|
||||
|
||||
case File.write(check_file, "Hello") do
|
||||
:ok ->
|
||||
Process.sleep(500)
|
||||
:ok
|
||||
|
||||
err ->
|
||||
Logger.info "Filesystem not up yet (#{inspect err})..."
|
||||
Logger.info("Filesystem not up yet (#{inspect(err)})...")
|
||||
Process.sleep(1000)
|
||||
do_checkup()
|
||||
end
|
||||
|
|
|
@ -17,8 +17,10 @@ defmodule Farmbot.System.Supervisor do
|
|||
supervisor(Farmbot.System.ConfigStorage, [])
|
||||
]
|
||||
|
||||
init_mods = Application.get_env(:farmbot, :init)
|
||||
|> Enum.map(fn(child) -> fb_init(child, [args, [name: child]]) end)
|
||||
supervise(children ++ init_mods, [strategy: :one_for_all])
|
||||
init_mods =
|
||||
Application.get_env(:farmbot, :init)
|
||||
|> Enum.map(fn child -> fb_init(child, [args, [name: child]]) end)
|
||||
|
||||
supervise(children ++ init_mods, strategy: :one_for_all)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ defmodule Farmbot.System do
|
|||
Please configure your `:system_tasks` behaviour!
|
||||
"""
|
||||
|
||||
@system_tasks Application.get_env(:farmbot, :behaviour)[:system_tasks] || Mix.raise error_msg
|
||||
@system_tasks Application.get_env(:farmbot, :behaviour)[:system_tasks] || Mix.raise(error_msg)
|
||||
|
||||
@typedoc "Reason for a task to execute. Should be human readable."
|
||||
@type reason :: binary
|
||||
|
@ -62,9 +62,11 @@ defmodule Farmbot.System do
|
|||
end
|
||||
|
||||
defp ensure_cs(count \\ 0)
|
||||
|
||||
defp ensure_cs(100) do
|
||||
ConfigStorage.start_link()
|
||||
end
|
||||
|
||||
defp ensure_cs(count) do
|
||||
Process.whereis(ConfigStorage) || ensure_cs(count + 1)
|
||||
end
|
||||
|
@ -76,32 +78,37 @@ defmodule Farmbot.System do
|
|||
@doc "Format an error for human consumption."
|
||||
def format_reason(reason) do
|
||||
raise "deleteme"
|
||||
rescue
|
||||
_ ->
|
||||
[_ | [_ | stack]] = System.stacktrace()
|
||||
stack = Enum.map(stack, fn(er) -> "\t#{inspect er}" end) |> Enum.join("\r\n")
|
||||
do_format_reason(reason) <> """
|
||||
rescue
|
||||
_ ->
|
||||
[_ | [_ | stack]] = System.stacktrace()
|
||||
stack = Enum.map(stack, fn er -> "\t#{inspect(er)}" end) |> Enum.join("\r\n")
|
||||
|
||||
environment: #{@env}
|
||||
source_ref: #{@ref}
|
||||
target: #{@target}
|
||||
Stacktrace:
|
||||
[
|
||||
#{stack}
|
||||
]
|
||||
"""
|
||||
do_format_reason(reason) <> """
|
||||
|
||||
environment: #{@env}
|
||||
source_ref: #{@ref}
|
||||
target: #{@target}
|
||||
Stacktrace:
|
||||
[
|
||||
#{stack}
|
||||
]
|
||||
"""
|
||||
end
|
||||
|
||||
# This mess of pattern matches cleans up erlang startup errors. It's very
|
||||
# recursive, and kind of cryptic, but should always produce a human readable
|
||||
# message that can be read by an end user.
|
||||
|
||||
defp do_format_reason({:error, {:shutdown, {:failed_to_start_child, Farmbot.Bootstrap.Supervisor, rest}}}) do
|
||||
defp do_format_reason({
|
||||
:error,
|
||||
{:shutdown, {:failed_to_start_child, Farmbot.Bootstrap.Supervisor, rest}}
|
||||
}) do
|
||||
do_format_reason(rest)
|
||||
end
|
||||
|
||||
defp do_format_reason({:error, {:shutdown, {:failed_to_start_child, child, rest}}}) do
|
||||
{failed_child, failed_reason} = enumerate_ftsc_error(child, rest)
|
||||
|
||||
"""
|
||||
Failed to start child: #{failed_child}
|
||||
reason: #{do_format_reason(failed_reason)}
|
||||
|
@ -124,7 +131,7 @@ defmodule Farmbot.System do
|
|||
reason |> to_string()
|
||||
end
|
||||
|
||||
defp do_format_reason({:error, reason}), do: inspect reason
|
||||
defp do_format_reason({:error, reason}), do: inspect(reason)
|
||||
|
||||
defp do_format_reason({:failed_connect, [{:to_address, {server, port}}, {_, _, reason}]}) do
|
||||
"""
|
||||
|
|
|
@ -10,25 +10,40 @@ defmodule Logger.Backends.Farmbot do
|
|||
{:ok, state}
|
||||
end
|
||||
|
||||
def handle_event({level, _gl, {Logger, message, {{year, month, day}, {hour, minute, second, _millisecond}}, metadata}}, state) do
|
||||
def handle_event(
|
||||
{
|
||||
level,
|
||||
_gl,
|
||||
{Logger, message, {{year, month, day}, {hour, minute, second, _millisecond}}, metadata}
|
||||
},
|
||||
state
|
||||
) do
|
||||
mod_split = (metadata[:module] || Logger) |> Module.split()
|
||||
|
||||
case mod_split do
|
||||
["Farmbot" | _] ->
|
||||
t = %DateTime{year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
calendar: Calendar.ISO,
|
||||
microsecond: {0, 0},
|
||||
second: second,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
zone_abbr: "UTC"} |> DateTime.to_iso8601
|
||||
t =
|
||||
%DateTime{
|
||||
year: year,
|
||||
month: month,
|
||||
day: day,
|
||||
hour: hour,
|
||||
minute: minute,
|
||||
calendar: Calendar.ISO,
|
||||
microsecond: {0, 0},
|
||||
second: second,
|
||||
std_offset: 0,
|
||||
time_zone: "Etc/UTC",
|
||||
utc_offset: 0,
|
||||
zone_abbr: "UTC"
|
||||
}
|
||||
|> DateTime.to_iso8601()
|
||||
|
||||
l = %Log{message: message, channels: [level], created_at: t}
|
||||
GenStage.async_info(Farmbot.Logger, {:log, l})
|
||||
_ -> :ok
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
{:ok, state}
|
||||
|
@ -41,5 +56,4 @@ defmodule Logger.Backends.Farmbot do
|
|||
def handle_info(_, state) do
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
end
|
||||
|
|
119
mix.exs
119
mix.exs
|
@ -1,62 +1,67 @@
|
|||
defmodule Farmbot.Mixfile do
|
||||
use Mix.Project
|
||||
@target System.get_env("MIX_TARGET") || "host"
|
||||
@version Path.join(__DIR__, "VERSION") |> File.read! |> String.trim
|
||||
@version Path.join(__DIR__, "VERSION") |> File.read!() |> String.trim()
|
||||
|
||||
defp commit() do
|
||||
{t,_} = System.cmd("git", ["log", "--pretty=format:%h", "-1"])
|
||||
{t, _} = System.cmd("git", ["log", "--pretty=format:%h", "-1"])
|
||||
t
|
||||
end
|
||||
|
||||
Mix.shell.info([:green, """
|
||||
Env
|
||||
MIX_TARGET: #{@target}
|
||||
MIX_ENV: #{Mix.env}
|
||||
""", :reset])
|
||||
Mix.shell().info([
|
||||
:green,
|
||||
"""
|
||||
Env
|
||||
MIX_TARGET: #{@target}
|
||||
MIX_ENV: #{Mix.env()}
|
||||
""",
|
||||
:reset
|
||||
])
|
||||
|
||||
def project do
|
||||
[app: :farmbot,
|
||||
description: "The Brains of the Farmbot Project",
|
||||
package: package(),
|
||||
test_coverage: [tool: ExCoveralls],
|
||||
version: @version,
|
||||
target: @target,
|
||||
commit: commit(),
|
||||
archives: [nerves_bootstrap: "~> 0.6.0"],
|
||||
build_embedded: Mix.env == :prod,
|
||||
start_permanent: Mix.env == :prod,
|
||||
deps_path: "deps/#{@target}",
|
||||
build_path: "_build/#{@target}",
|
||||
lockfile: "mix.lock.#{@target}",
|
||||
config_path: "config/config.exs",
|
||||
lockfile: "mix.lock",
|
||||
elixirc_paths: elixirc_paths(Mix.env, @target),
|
||||
aliases: aliases(@target),
|
||||
deps: deps() ++ deps(@target),
|
||||
dialyzer: [
|
||||
plt_add_deps: :transitive,
|
||||
flags: []
|
||||
],
|
||||
preferred_cli_env: [
|
||||
"test": :test,
|
||||
"coveralls": :test,
|
||||
"coveralls.detail": :test,
|
||||
"coveralls.post": :test,
|
||||
"coveralls.html": :test,
|
||||
"coveralls.travis": :test
|
||||
],
|
||||
source_url: "https://github.com/Farmbot/farmbot_os",
|
||||
homepage_url: "http://farmbot.io",
|
||||
docs: [
|
||||
main: "Farmbot",
|
||||
logo: "priv/static/farmbot_logo.png",
|
||||
extras: [
|
||||
"docs/BUILDING.md",
|
||||
"docs/FAQ.md",
|
||||
"README.md"
|
||||
]
|
||||
]
|
||||
]
|
||||
[
|
||||
app: :farmbot,
|
||||
description: "The Brains of the Farmbot Project",
|
||||
package: package(),
|
||||
test_coverage: [tool: ExCoveralls],
|
||||
version: @version,
|
||||
target: @target,
|
||||
commit: commit(),
|
||||
archives: [nerves_bootstrap: "~> 0.6.0"],
|
||||
build_embedded: Mix.env() == :prod,
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps_path: "deps/#{@target}",
|
||||
build_path: "_build/#{@target}",
|
||||
lockfile: "mix.lock.#{@target}",
|
||||
config_path: "config/config.exs",
|
||||
lockfile: "mix.lock",
|
||||
elixirc_paths: elixirc_paths(Mix.env(), @target),
|
||||
aliases: aliases(@target),
|
||||
deps: deps() ++ deps(@target),
|
||||
dialyzer: [
|
||||
plt_add_deps: :transitive,
|
||||
flags: []
|
||||
],
|
||||
preferred_cli_env: [
|
||||
test: :test,
|
||||
coveralls: :test,
|
||||
"coveralls.detail": :test,
|
||||
"coveralls.post": :test,
|
||||
"coveralls.html": :test,
|
||||
"coveralls.travis": :test
|
||||
],
|
||||
source_url: "https://github.com/Farmbot/farmbot_os",
|
||||
homepage_url: "http://farmbot.io",
|
||||
docs: [
|
||||
main: "Farmbot",
|
||||
logo: "priv/static/farmbot_logo.png",
|
||||
extras: [
|
||||
"docs/BUILDING.md",
|
||||
"docs/FAQ.md",
|
||||
"README.md"
|
||||
]
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
def application do
|
||||
|
@ -66,24 +71,19 @@ defmodule Farmbot.Mixfile do
|
|||
defp deps do
|
||||
[
|
||||
{:nerves, "~> 0.7.5", runtime: false},
|
||||
|
||||
{:gen_mqtt, "~> 0.3.1"},
|
||||
{:vmq_commons, github: "farmbot-labs/vmq_commons", override: true},
|
||||
|
||||
{:gen_stage, "~> 0.12"},
|
||||
|
||||
{:poison, "~> 3.0"},
|
||||
{:ex_json_schema, "~> 0.5.3"},
|
||||
{:rsa, "~> 0.0.1"},
|
||||
{:httpoison, "~> 0.13.0"},
|
||||
|
||||
{:tzdata, "~> 0.1.201601", override: true},
|
||||
{:timex, "~> 3.1.13"},
|
||||
|
||||
# {:fs, "~> 0.9.1"},
|
||||
{:nerves_uart, "0.1.2"},
|
||||
{:uuid, "~> 1.1" },
|
||||
|
||||
{:uuid, "~> 1.1"},
|
||||
{:cowboy, "~> 1.0.0"},
|
||||
{:plug, "~> 1.0"},
|
||||
{:ecto, "~> 2.2.2"},
|
||||
|
@ -97,12 +97,12 @@ defmodule Farmbot.Mixfile do
|
|||
{:ex_doc, "~> 0.14", only: :dev},
|
||||
{:excoveralls, "~> 0.6", only: :test},
|
||||
{:mock, "~> 0.2.0", only: :test}
|
||||
|
||||
]
|
||||
end
|
||||
|
||||
defp deps(target) do
|
||||
[ system(target),
|
||||
[
|
||||
system(target),
|
||||
{:bootloader, "~> 0.1"},
|
||||
{:nerves_runtime, "~> 0.4"},
|
||||
{:nerves_network, "~> 0.3"},
|
||||
|
@ -138,8 +138,9 @@ defmodule Farmbot.Mixfile do
|
|||
defp aliases("host"), do: []
|
||||
|
||||
defp aliases(_system) do
|
||||
["deps.precompile": ["nerves.precompile", "deps.precompile"],
|
||||
"deps.loadpaths": ["deps.loadpaths", "nerves.loadpaths"]
|
||||
[
|
||||
"deps.precompile": ["nerves.precompile", "deps.precompile"],
|
||||
"deps.loadpaths": ["deps.loadpaths", "nerves.loadpaths"]
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,13 +10,13 @@ defmodule Farmbot.Host.Bootstrap.Configurator do
|
|||
# Get out authorization data out of the environment.
|
||||
# for host environment this will be configured at compile time.
|
||||
# for target environment it will be configured by `configurator`.
|
||||
email = Application.get_env(:farmbot, :authorization)[:email ] || raise error("email")
|
||||
pass = Application.get_env(:farmbot, :authorization)[:password] || raise error("password")
|
||||
server = Application.get_env(:farmbot, :authorization)[:server ] || raise error("server")
|
||||
ConfigStorage.update_config_value(:string, "authorization", "email", email)
|
||||
email = Application.get_env(:farmbot, :authorization)[:email] || raise error("email")
|
||||
pass = Application.get_env(:farmbot, :authorization)[:password] || raise error("password")
|
||||
server = Application.get_env(:farmbot, :authorization)[:server] || raise error("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)
|
||||
ConfigStorage.update_config_value(:string, "authorization", "server", server)
|
||||
ConfigStorage.update_config_value(:string, "authorization", "token", nil)
|
||||
:ignore
|
||||
end
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ defmodule Farmbot.Host.SystemTasks do
|
|||
require Logger
|
||||
|
||||
def factory_reset(reason) do
|
||||
Logger.error "Host factory reset: #{reason}"
|
||||
Logger.error("Host factory reset: #{reason}")
|
||||
shutdown(reason)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Farmbot.Target.Bootstrap.Configurator do
|
||||
defmodule Farmbot.Target.Bootstrap.Configurator do
|
||||
@moduledoc """
|
||||
This init module is used to bring up initial configuration.
|
||||
If it can't find a configuration it will bring up a captive portal for a device to connect to.
|
||||
|
@ -20,12 +20,15 @@ defmodule Farmbot.Target.Bootstrap.Configurator do
|
|||
reset and the user will need to configureate again.
|
||||
"""
|
||||
def start_link(_, opts) do
|
||||
Logger.info "Configuring Farmbot."
|
||||
Logger.info("Configuring Farmbot.")
|
||||
supervisor = Supervisor.start_link(__MODULE__, [self()], opts)
|
||||
|
||||
case supervisor do
|
||||
{:ok, pid} ->
|
||||
wait(pid)
|
||||
:ignore -> :ignore
|
||||
|
||||
:ignore ->
|
||||
:ignore
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -40,14 +43,22 @@ defmodule Farmbot.Target.Bootstrap.Configurator do
|
|||
|
||||
def init(_) do
|
||||
first_boot? = ConfigStorage.get_config_value(:bool, "settings", "first_boot")
|
||||
|
||||
if first_boot? do
|
||||
Logger.info "Building new configuration."
|
||||
Logger.info("Building new configuration.")
|
||||
import Supervisor.Spec
|
||||
:ets.new(:session, [:named_table, :public, read_concurrency: true])
|
||||
|
||||
children = [
|
||||
Plug.Adapters.Cowboy.child_spec(:http, Farmbot.Target.Bootstrap.Configurator.Router, [], [port: 80]),
|
||||
Plug.Adapters.Cowboy.child_spec(
|
||||
:http,
|
||||
Farmbot.Target.Bootstrap.Configurator.Router,
|
||||
[],
|
||||
port: 80
|
||||
),
|
||||
worker(Farmbot.Target.Bootstrap.Configurator.CaptivePortal, [])
|
||||
]
|
||||
|
||||
opts = [strategy: :one_for_one]
|
||||
supervise(children, opts)
|
||||
else
|
||||
|
|
|
@ -13,7 +13,7 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do
|
|||
require Logger
|
||||
|
||||
@hostapd_conf_file "hostapd.conf"
|
||||
@hostapd_pid_file "hostapd.pid"
|
||||
@hostapd_pid_file "hostapd.pid"
|
||||
|
||||
@doc ~s"""
|
||||
Example:
|
||||
|
@ -26,17 +26,18 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do
|
|||
|
||||
def init(opts) do
|
||||
# We want to know if something does.
|
||||
Process.flag :trap_exit, true
|
||||
Process.flag(:trap_exit, true)
|
||||
interface = Keyword.fetch!(opts, :interface)
|
||||
Logger.info ">> is starting hostapd on #{interface}"
|
||||
Logger.info(">> is starting hostapd on #{interface}")
|
||||
|
||||
{hostapd_port, hostapd_os_pid} = setup_hostapd(interface, "192.168.24.1")
|
||||
|
||||
state = %State{
|
||||
state = %State{
|
||||
hostapd: {hostapd_port, hostapd_os_pid},
|
||||
interface: interface,
|
||||
ip_addr: "192.168.24.1"
|
||||
}
|
||||
|
||||
{:ok, state}
|
||||
end
|
||||
|
||||
|
@ -46,36 +47,47 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do
|
|||
# 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}"
|
||||
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_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 =
|
||||
"ip" |> System.cmd(["link", "set", "#{interface}", "up"])
|
||||
"ip"
|
||||
|> System.cmd(["link", "set", "#{interface}", "up"])
|
||||
|> print_cmd
|
||||
|
||||
:ok =
|
||||
"ip" |> System.cmd(["addr", "add", "#{ip_addr}/24", "dev", "#{interface}"])
|
||||
"ip"
|
||||
|> System.cmd(["addr", "add", "#{ip_addr}/24", "dev", "#{interface}"])
|
||||
|> print_cmd
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp hostapd_ip_settings_down(interface, ip_addr) do
|
||||
:ok =
|
||||
"ip" |> System.cmd(["link", "set", "#{interface}", "down"])
|
||||
"ip"
|
||||
|> System.cmd(["link", "set", "#{interface}", "down"])
|
||||
|> print_cmd
|
||||
|
||||
:ok =
|
||||
"ip" |> System.cmd(["addr", "del", "#{ip_addr}/24", "dev", "#{interface}"])
|
||||
"ip"
|
||||
|> System.cmd(["addr", "del", "#{ip_addr}/24", "dev", "#{interface}"])
|
||||
|> print_cmd
|
||||
|
||||
:ok =
|
||||
"ip" |> System.cmd(["link", "set", "#{interface}", "up"])
|
||||
"ip"
|
||||
|> System.cmd(["link", "set", "#{interface}", "up"])
|
||||
|> print_cmd
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
|
@ -91,28 +103,29 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do
|
|||
end
|
||||
|
||||
defp build_ssid do
|
||||
node_str =
|
||||
node() |> Atom.to_string
|
||||
[name, "farmbot-" <> id] =
|
||||
node_str |> String.split("@")
|
||||
node_str = node() |> Atom.to_string()
|
||||
[name, "farmbot-" <> id] = node_str |> String.split("@")
|
||||
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(["15", "#{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
|
||||
|
||||
def handle_info({port, {:data, data}}, state) do
|
||||
{hostapd_port, _} = state.hostapd
|
||||
|
||||
cond do
|
||||
port == hostapd_port ->
|
||||
handle_hostapd(data, state)
|
||||
true -> {:noreply, state}
|
||||
|
||||
true ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -126,13 +139,14 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do
|
|||
defp handle_hostapd(_, state), do: {:noreply, state}
|
||||
|
||||
def terminate(_, state) do
|
||||
Logger.info ">> is stopping hostapd"
|
||||
Logger.info(">> is stopping hostapd")
|
||||
{_hostapd_port, hostapd_pid} = state.hostapd
|
||||
:ok = kill(hostapd_pid)
|
||||
hostapd_ip_settings_down(state.interface, state.ip_addr)
|
||||
File.rm_rf! "/tmp/hostapd"
|
||||
File.rm_rf!("/tmp/hostapd")
|
||||
end
|
||||
end
|
||||
|
||||
use GenServer
|
||||
require Logger
|
||||
@interface "wlan0"
|
||||
|
@ -142,14 +156,14 @@ defmodule Farmbot.Target.Bootstrap.Configurator.CaptivePortal do
|
|||
end
|
||||
|
||||
def init([]) do
|
||||
Logger.debug "Starting captive portal."
|
||||
Logger.debug("Starting captive portal.")
|
||||
{:ok, hostapd} = Hostapd.start_link(interface: @interface)
|
||||
{:ok, dhcp_server} = DHCPServer.start_link(interface: @interface)
|
||||
{:ok, %{hostapd: hostapd, dhcp_server: dhcp_server}}
|
||||
end
|
||||
|
||||
def terminate(_, state) do
|
||||
Logger.debug "Stopping captive portal."
|
||||
Logger.debug("Stopping captive portal.")
|
||||
GenServer.stop(state.hostapd, :normal)
|
||||
GenServer.stop(state.dhcp_server, :normal)
|
||||
end
|
||||
|
|
|
@ -3,37 +3,41 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
|
|||
|
||||
use Plug.Router
|
||||
|
||||
if Mix.env == :dev do
|
||||
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
|
||||
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: render_page(conn, "index")
|
||||
get("/", do: render_page(conn, "index"))
|
||||
|
||||
get "/network" do
|
||||
interfaces = Nerves.NetworkInterface.interfaces()
|
||||
info = [interfaces: Map.new(interfaces, fn(iface) ->
|
||||
if String.first(iface) == "w" do
|
||||
{iface, %{type: :wireless, ssids: do_iw_scan(iface)}}
|
||||
else
|
||||
{iface, %{type: :wired}}
|
||||
end
|
||||
end)]
|
||||
|
||||
info = [
|
||||
interfaces: Map.new(interfaces, fn iface ->
|
||||
if String.first(iface) == "w" do
|
||||
{iface, %{type: :wireless, ssids: do_iw_scan(iface)}}
|
||||
else
|
||||
{iface, %{type: :wired}}
|
||||
end
|
||||
end)
|
||||
]
|
||||
|
||||
render_page(conn, "network", info)
|
||||
end
|
||||
|
||||
defp do_iw_scan(iface) do
|
||||
defp do_iw_scan(iface) do
|
||||
case System.cmd("iw", [iface, "scan", "ap-force"]) do
|
||||
{res, 0} -> res |> clean_ssid
|
||||
e -> raise "Could not scan for wifi: #{inspect e}"
|
||||
e -> raise "Could not scan for wifi: #{inspect(e)}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -42,9 +46,9 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
|
|||
|> 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)
|
||||
|> 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
|
||||
|
||||
get "/firmware" do
|
||||
|
@ -52,15 +56,15 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
|
|||
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])
|
||||
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
|
||||
{:ok, _, conn} = read_body conn
|
||||
{:ok, _, conn} = read_body(conn)
|
||||
:ok = conn.body_params |> sort_network_configs |> input_network_configs
|
||||
|
||||
redir(conn, "/firmware")
|
||||
|
@ -68,8 +72,13 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
|
|||
|
||||
get "/finish" do
|
||||
conn = render_page(conn, "finish")
|
||||
Supervisor.terminate_child(Farmbot.Target.Bootstrap.Configurator, Farmbot.Target.Bootstrap.Configurator.CaptivePortal)
|
||||
Supervisor.stop Farmbot.Target.Bootstrap.Configurator, :normal
|
||||
|
||||
Supervisor.terminate_child(
|
||||
Farmbot.Target.Bootstrap.Configurator,
|
||||
Farmbot.Target.Bootstrap.Configurator.CaptivePortal
|
||||
)
|
||||
|
||||
Supervisor.stop(Farmbot.Target.Bootstrap.Configurator, :normal)
|
||||
conn
|
||||
end
|
||||
|
||||
|
@ -81,10 +90,12 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
|
|||
|
||||
defp sort_network_configs([{key, val} | rest], acc) do
|
||||
[iface, key] = String.split(key, "_")
|
||||
acc = case acc[iface] do
|
||||
map when is_map(map) -> %{acc | iface => Map.merge(acc[iface], %{key => val})}
|
||||
nil -> Map.put(acc, iface, %{key => val})
|
||||
end
|
||||
|
||||
acc =
|
||||
case acc[iface] do
|
||||
map when is_map(map) -> %{acc | iface => Map.merge(acc[iface], %{key => val})}
|
||||
nil -> Map.put(acc, iface, %{key => val})
|
||||
end
|
||||
|
||||
sort_network_configs(rest, acc)
|
||||
end
|
||||
|
@ -97,9 +108,10 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
|
|||
conf_map |> Map.to_list() |> input_network_configs
|
||||
end
|
||||
|
||||
defp input_network_configs([{iface, settings} | rest]) do
|
||||
defp input_network_configs([{iface, settings} | rest]) do
|
||||
if settings["enable"] == "on" do
|
||||
Logger.info "inputting #{iface} - #{inspect settings}"
|
||||
Logger.info("inputting #{iface} - #{inspect(settings)}")
|
||||
|
||||
case settings["type"] do
|
||||
"wireless" ->
|
||||
%ConfigStorage.NetworkInterface{
|
||||
|
@ -110,14 +122,13 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
|
|||
security: "WPA-PSK",
|
||||
ipv4_method: "dhcp"
|
||||
}
|
||||
|
||||
"wired" ->
|
||||
%ConfigStorage.NetworkInterface{name: iface,
|
||||
type: "wired",
|
||||
ipv4_method: "dhcp"
|
||||
}
|
||||
%ConfigStorage.NetworkInterface{name: iface, type: "wired", ipv4_method: "dhcp"}
|
||||
end
|
||||
|> ConfigStorage.insert!()
|
||||
end
|
||||
|
||||
input_network_configs(rest)
|
||||
end
|
||||
|
||||
|
@ -126,33 +137,40 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
|
|||
end
|
||||
|
||||
post "/configure_firmware" do
|
||||
{:ok, _, conn} = read_body conn
|
||||
{: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.
|
||||
# TODO Flash firmware here.
|
||||
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!")
|
||||
|
||||
_ ->
|
||||
send_resp(conn, 500, "Bad firmware_hardware!")
|
||||
end
|
||||
end
|
||||
|
||||
post "/configure_credentials" do
|
||||
{:ok, _, conn} = read_body conn
|
||||
{: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", "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)
|
||||
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.")
|
||||
|
||||
_ ->
|
||||
send_resp(conn, 500, "invalid request.")
|
||||
end
|
||||
end
|
||||
|
||||
match _, do: send_resp(conn, 404, "Page not found")
|
||||
match(_, do: send_resp(conn, 404, "Page not found"))
|
||||
|
||||
defp redir(conn, loc) do
|
||||
conn
|
||||
|
@ -164,7 +182,7 @@ defmodule Farmbot.Target.Bootstrap.Configurator.Router do
|
|||
page
|
||||
|> template_file()
|
||||
|> EEx.eval_file(info)
|
||||
|> fn(contents) -> send_resp(conn, 200, contents) end.()
|
||||
|> (fn contents -> send_resp(conn, 200, contents) end).()
|
||||
rescue
|
||||
e -> send_resp(conn, 500, "Failed to render page: #{page} inspect: #{Exception.message(e)}")
|
||||
end
|
||||
|
|
|
@ -20,18 +20,24 @@ defmodule Farmbot.Target.Network do
|
|||
end
|
||||
|
||||
def init(name) do
|
||||
Logger.debug "Starting NetworkWatcher - #{name}"
|
||||
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]
|
||||
_status = get_in(registry, [:state, :network_interface, state.name])
|
||||
|
||||
connected =
|
||||
match?(
|
||||
{:ok, {:hostent, 'nerves-project.org', [], :inet, 4, _}},
|
||||
Farmbot.Target.Network.test_dns()
|
||||
)
|
||||
|
||||
connected = match?({:ok, {:hostent, 'nerves-project.org', [], :inet, 4, _}}, Farmbot.Target.Network.test_dns())
|
||||
if connected do
|
||||
Logger.debug "Connected!"
|
||||
Logger.debug("Connected!")
|
||||
end
|
||||
|
||||
{:noreply, %{state | connected: connected}}
|
||||
end
|
||||
end
|
||||
|
@ -50,14 +56,20 @@ defmodule Farmbot.Target.Network do
|
|||
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'))
|
||||
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."
|
||||
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."
|
||||
Logger.warn("No dns. Trying again.")
|
||||
Process.send_after(self(), :time, 1000)
|
||||
end
|
||||
|
||||
{:ok, %{state | dns: dns}}
|
||||
end
|
||||
end
|
||||
|
@ -67,33 +79,43 @@ defmodule Farmbot.Target.Network do
|
|||
end
|
||||
|
||||
def init([]) do
|
||||
Logger.debug "Starting up network!"
|
||||
Logger.debug("Starting up network!")
|
||||
import Ecto.Query
|
||||
interfaces = ConfigStorage.all(from i in NetworkInterface)
|
||||
children = (Enum.map(interfaces, fn(interface) ->
|
||||
start_iface(interface)
|
||||
worker(NetworkWatcher, [interface.name])
|
||||
end)) ++ [worker(NTP, [])]
|
||||
{:ok, sup} = supervise(children, [strategy: :one_for_one])
|
||||
interfaces = ConfigStorage.all(from(i in NetworkInterface))
|
||||
|
||||
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, []
|
||||
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}"]
|
||||
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 > 1507149578330507924 do
|
||||
|
||||
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}"
|
||||
if rem(time, 10) == 0, do: Logger.debug("Waiting for time: #{time}")
|
||||
wait_for_network()
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,14 +4,11 @@ defmodule Farmbot.Target.SystemTasks do
|
|||
@behaviour Farmbot.System
|
||||
|
||||
def factory_reset(_reason) do
|
||||
|
||||
end
|
||||
|
||||
def reboot(_reason) do
|
||||
|
||||
end
|
||||
|
||||
def shutdown(_reason) do
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,41 +2,39 @@ defmodule Farmbot.System.ConfigStorage.Migrations.AddEAVTables do
|
|||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
|
||||
create table("groups") do
|
||||
add :group_name, :string
|
||||
add(:group_name, :string)
|
||||
end
|
||||
|
||||
create table("string_values") do
|
||||
add :value, :string
|
||||
add(:value, :string)
|
||||
end
|
||||
|
||||
create table("bool_values") do
|
||||
add :value, :boolean
|
||||
add(:value, :boolean)
|
||||
end
|
||||
|
||||
create table("float_values") do
|
||||
add :value, :float
|
||||
add(:value, :float)
|
||||
end
|
||||
|
||||
create table("configs") do
|
||||
add :group_id, references(:groups), null: false
|
||||
add :string_value_id, references(:string_values)
|
||||
add :bool_value_id, references(:bool_values)
|
||||
add :float_value_id, references(:float_values)
|
||||
add :key, :string
|
||||
add(:group_id, references(:groups), null: false)
|
||||
add(:string_value_id, references(:string_values))
|
||||
add(:bool_value_id, references(:bool_values))
|
||||
add(:float_value_id, references(:float_values))
|
||||
add(:key, :string)
|
||||
end
|
||||
|
||||
create table("network_interfaces") do
|
||||
add :name, :string, null: false
|
||||
add :type, :string, null: false
|
||||
add(:name, :string, null: false)
|
||||
add(:type, :string, null: false)
|
||||
|
||||
add :ssid, :string
|
||||
add :psk, :string
|
||||
add :security, :string
|
||||
|
||||
add :ipv4_method, :string
|
||||
add(:ssid, :string)
|
||||
add(:psk, :string)
|
||||
add(:security, :string)
|
||||
|
||||
add(:ipv4_method, :string)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -21,7 +21,9 @@ defmodule Farmbot.System.ConfigStorage.Migrations.SeedGroups do
|
|||
|
||||
defp populate_config_values do
|
||||
for name <- @group_names do
|
||||
[group_id] = (from g in Group, where: g.group_name == ^name, select: g.id) |> ConfigStorage.all()
|
||||
[group_id] =
|
||||
from(g in Group, where: g.group_name == ^name, select: g.id) |> ConfigStorage.all()
|
||||
|
||||
populate_config_values(name, group_id)
|
||||
end
|
||||
end
|
||||
|
@ -39,25 +41,24 @@ defmodule Farmbot.System.ConfigStorage.Migrations.SeedGroups do
|
|||
end
|
||||
|
||||
defp populate_config_values("hardware_params", group_id) do
|
||||
|
||||
end
|
||||
|
||||
defp populate_config_values("settings", group_id) do
|
||||
create_value(BoolValue, false) |> create_config(group_id, "os_auto_update")
|
||||
create_value(BoolValue, true) |> create_config(group_id, "first_boot")
|
||||
create_value(BoolValue, true) |> create_config(group_id, "first_party_farmware")
|
||||
create_value(BoolValue, true) |> create_config(group_id, "first_boot")
|
||||
create_value(BoolValue, true) |> create_config(group_id, "first_party_farmware")
|
||||
create_value(StringValue, nil) |> create_config(group_id, "timezone")
|
||||
end
|
||||
|
||||
defp populate_config_values("user_env", group_id) do
|
||||
|
||||
end
|
||||
|
||||
|
||||
defp create_config(value, group_id, key) do
|
||||
%Config{group_id: group_id,
|
||||
key: key}
|
||||
|> Map.put(:"#{Module.split(value.__struct__) |> List.last() |> Macro.underscore()}_id", value.id)
|
||||
%Config{group_id: group_id, key: key}
|
||||
|> Map.put(
|
||||
:"#{Module.split(value.__struct__) |> List.last() |> Macro.underscore()}_id",
|
||||
value.id
|
||||
)
|
||||
|> Config.changeset()
|
||||
|> ConfigStorage.insert!()
|
||||
end
|
||||
|
|
|
@ -3,15 +3,15 @@ defmodule Farmbot.Repo.Migrations.AddFarmEventsTable do
|
|||
|
||||
def change do
|
||||
create table("farm_events", primary_key: false) do
|
||||
add :id, :integer
|
||||
add :start_time, :utc_datetime
|
||||
add :end_time, :utc_datetime
|
||||
add :repeat, :integer
|
||||
add :time_unit, :string
|
||||
add :executable_type, :string
|
||||
add :executable_id, :integer
|
||||
add(:id, :integer)
|
||||
add(:start_time, :utc_datetime)
|
||||
add(:end_time, :utc_datetime)
|
||||
add(:repeat, :integer)
|
||||
add(:time_unit, :string)
|
||||
add(:executable_type, :string)
|
||||
add(:executable_id, :integer)
|
||||
end
|
||||
|
||||
create unique_index("farm_events", [:id])
|
||||
create(unique_index("farm_events", [:id]))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,11 +3,12 @@ defmodule Farmbot.Repo.Migrations.AddPeripheralsTable do
|
|||
|
||||
def change do
|
||||
create table("peripherals", primary_key: false) do
|
||||
add :id, :integer
|
||||
add :pin, :integer
|
||||
add :mode, :integer
|
||||
add :label, :string
|
||||
add(:id, :integer)
|
||||
add(:pin, :integer)
|
||||
add(:mode, :integer)
|
||||
add(:label, :string)
|
||||
end
|
||||
create unique_index("peripherals", [:id])
|
||||
|
||||
create(unique_index("peripherals", [:id]))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,13 +3,13 @@ defmodule Farmbot.Repo.Migrations.AddSequencesTable do
|
|||
|
||||
def change do
|
||||
create table("sequences", primary_key: false) do
|
||||
add :id, :integer
|
||||
add :name, :string
|
||||
add :kind, :string, default: "sequence"
|
||||
add :args, :text
|
||||
add :body, :text
|
||||
add(:id, :integer)
|
||||
add(:name, :string)
|
||||
add(:kind, :string, default: "sequence")
|
||||
add(:args, :text)
|
||||
add(:body, :text)
|
||||
end
|
||||
|
||||
create unique_index("sequences", [:id])
|
||||
create(unique_index("sequences", [:id]))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,10 +3,10 @@ defmodule Farmbot.Repo.Migrations.AddRegimensTable do
|
|||
|
||||
def change do
|
||||
create table("regimens", primary_key: false) do
|
||||
add :id, :integer
|
||||
add :name, :string
|
||||
add(:id, :integer)
|
||||
add(:name, :string)
|
||||
end
|
||||
|
||||
create unique_index("regimens", [:id])
|
||||
create(unique_index("regimens", [:id]))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,11 +3,10 @@ defmodule Farmbot.Repo.Migrations.AddToolsTable do
|
|||
|
||||
def change do
|
||||
create table("tools", primary_key: false) do
|
||||
add :id, :integer
|
||||
add :name, :string
|
||||
add(:id, :integer)
|
||||
add(:name, :string)
|
||||
end
|
||||
|
||||
create unique_index("tools", [:id])
|
||||
|
||||
create(unique_index("tools", [:id]))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,11 +3,10 @@ defmodule Farmbot.Repo.Migrations.AddToolSlotsTable do
|
|||
|
||||
def change do
|
||||
create table("tool_slots", primary_key: false) do
|
||||
add :id, :integer
|
||||
add :tool_id, :integer
|
||||
add(:id, :integer)
|
||||
add(:tool_id, :integer)
|
||||
end
|
||||
|
||||
create unique_index("tool_slots", [:id])
|
||||
|
||||
create(unique_index("tool_slots", [:id]))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,16 +3,15 @@ defmodule Farmbot.Repo.Migrations.AddPointsTable do
|
|||
|
||||
def change do
|
||||
create table("points", primary_key: false) do
|
||||
add :id, :integer
|
||||
add :name, :string
|
||||
add :x, :float
|
||||
add :y, :float
|
||||
add :z, :float
|
||||
add :meta, :text
|
||||
add :pointer_type, :string
|
||||
add(:id, :integer)
|
||||
add(:name, :string)
|
||||
add(:x, :float)
|
||||
add(:y, :float)
|
||||
add(:z, :float)
|
||||
add(:meta, :text)
|
||||
add(:pointer_type, :string)
|
||||
end
|
||||
|
||||
create unique_index("points", [:id])
|
||||
|
||||
create(unique_index("points", [:id]))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,10 +3,9 @@ defmodule Farmbot.Repo.Migrations.AddGenericPointersTable do
|
|||
|
||||
def change do
|
||||
create table("generic_pointers", primary_key: false) do
|
||||
add :id, :integer
|
||||
add(:id, :integer)
|
||||
end
|
||||
|
||||
create unique_index("generic_pointers", [:id])
|
||||
|
||||
create(unique_index("generic_pointers", [:id]))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,24 +1,23 @@
|
|||
# This sets the default release built by `mix release`
|
||||
# This sets the default environment used by `mix release`
|
||||
use Mix.Releases.Config,
|
||||
# This sets the default release built by `mix release`
|
||||
default_release: :default,
|
||||
# This sets the default environment used by `mix release`
|
||||
default_environment: :prod
|
||||
default_release: :default,
|
||||
default_environment: :prod
|
||||
|
||||
# For a full list of config options for both releases
|
||||
# and environments, visit https://hexdocs.pm/distillery/configuration.html
|
||||
|
||||
|
||||
# You may define one or more environments in this file,
|
||||
# an environment's settings will override those of a release
|
||||
# when building in that environment, this combination of release
|
||||
# and environment configuration is called a profile
|
||||
|
||||
environment :dev do
|
||||
set cookie: :"gz`tgx[zM,ueL[g{Ji62{jiawNDZHH~PGkNQLa&R>R7c0SKziff4L,*&ZNG)(qu0"
|
||||
set(cookie: :"gz`tgx[zM,ueL[g{Ji62{jiawNDZHH~PGkNQLa&R>R7c0SKziff4L,*&ZNG)(qu0")
|
||||
end
|
||||
|
||||
environment :prod do
|
||||
set cookie: :"gz`tgx[zM,ueL[g{Ji62{jiawNDZHH~PGkNQLa&R>R7c0SKziff4L,*&ZNG)(qu0"
|
||||
set(cookie: :"gz`tgx[zM,ueL[g{Ji62{jiawNDZHH~PGkNQLa&R>R7c0SKziff4L,*&ZNG)(qu0")
|
||||
end
|
||||
|
||||
# You may define one or more releases in this file.
|
||||
|
@ -27,13 +26,13 @@ end
|
|||
# will be used by default
|
||||
|
||||
release :farmbot do
|
||||
set version: current_version(:farmbot)
|
||||
set(version: current_version(:farmbot))
|
||||
# plugin Bootloader.Plugin
|
||||
if System.get_env("NERVES_SYSTEM") do
|
||||
set dev_mode: false
|
||||
set include_src: false
|
||||
set include_erts: System.get_env("ERL_LIB_DIR")
|
||||
set include_system_libs: System.get_env("ERL_SYSTEM_LIB_DIR")
|
||||
set vm_args: "rel/vm.args"
|
||||
set(dev_mode: false)
|
||||
set(include_src: false)
|
||||
set(include_erts: System.get_env("ERL_LIB_DIR"))
|
||||
set(include_system_libs: System.get_env("ERL_SYSTEM_LIB_DIR"))
|
||||
set(vm_args: "rel/vm.args")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,9 +6,18 @@ defmodule Farmbot.Bootstrap.AuthorizationTest do
|
|||
@moduletag :farmbot_api
|
||||
|
||||
setup do
|
||||
email = Application.get_env(:farmbot, :authorization)[:email ] || raise Auth.Error, "No email provided."
|
||||
pass = Application.get_env(:farmbot, :authorization)[:password] || raise Auth.Error, "No password provided."
|
||||
server = Application.get_env(:farmbot, :authorization)[:server ] || raise Auth.Error, "No server provided."
|
||||
email =
|
||||
Application.get_env(:farmbot, :authorization)[:email] ||
|
||||
raise Auth.Error, "No email provided."
|
||||
|
||||
pass =
|
||||
Application.get_env(:farmbot, :authorization)[:password] ||
|
||||
raise Auth.Error, "No password provided."
|
||||
|
||||
server =
|
||||
Application.get_env(:farmbot, :authorization)[:server] ||
|
||||
raise Auth.Error, "No server provided."
|
||||
|
||||
[email: email, password: pass, server: server]
|
||||
end
|
||||
|
||||
|
@ -30,6 +39,9 @@ defmodule Farmbot.Bootstrap.AuthorizationTest do
|
|||
assert match?({:error, _}, res)
|
||||
{:error, message} = res
|
||||
# This shoud _probably_ be fixed on the API.
|
||||
assert message == "Failed to authorize with the Farmbot web application at: #{ctx.server} with code: #{422}"
|
||||
assert message ==
|
||||
"Failed to authorize with the Farmbot web application at: #{ctx.server} with code: #{
|
||||
422
|
||||
}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ defmodule Farmbot.BotState.InformationalSettingsTest do
|
|||
|
||||
use ExUnit.Case
|
||||
|
||||
@version Mix.Project.config[:version]
|
||||
@version Mix.Project.config()[:version]
|
||||
|
||||
setup do
|
||||
{:ok, bot_state_tracker} = Farmbot.BotState.start_link()
|
||||
|
@ -20,11 +20,12 @@ defmodule Farmbot.BotState.InformationalSettingsTest do
|
|||
|
||||
test "checks sync_status enum" do
|
||||
import Settings.SyncStatus
|
||||
|
||||
for sts <- [:locked, :maintenance, :sync_error, :sync_now, :synced, :syncing, :unknown] do
|
||||
assert status(sts)
|
||||
end
|
||||
|
||||
assert_raise RuntimeError, "unknown sync status: out_of_syc", fn() ->
|
||||
assert_raise RuntimeError, "unknown sync status: out_of_syc", fn ->
|
||||
status(:out_of_syc)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,8 @@ defmodule Farmbot.BotState.Lib.PartitionTest do
|
|||
|
||||
alias Farmbot.BotState.Lib.Partition
|
||||
|
||||
defstruct [hello: 1, world: 1]
|
||||
defstruct hello: 1, world: 1
|
||||
|
||||
test "dispatches info properly." do
|
||||
public = %__MODULE__{}
|
||||
private = %Partition.PrivateState{bot_state_tracker: self(), public: public}
|
||||
|
@ -20,7 +21,7 @@ defmodule Farmbot.BotState.Lib.PartitionTest do
|
|||
|
||||
defmodule TestPartA do
|
||||
@moduledoc false
|
||||
defstruct [some_state_data: :default]
|
||||
defstruct some_state_data: :default
|
||||
use Farmbot.BotState.Lib.Partition
|
||||
|
||||
def force_push(ser), do: GenServer.call(ser, :force)
|
||||
|
@ -48,16 +49,15 @@ defmodule Farmbot.BotState.Lib.PartitionTest do
|
|||
def get_res(ser), do: GenServer.call(ser, :get_res)
|
||||
|
||||
def handle_call({:put_test_fn, fun}, _, _), do: {:reply, :ok, fun}
|
||||
def handle_call(:get_res, _, res), do: {:reply, res, nil}
|
||||
def handle_call(:get_res, _, res), do: {:reply, res, nil}
|
||||
|
||||
def handle_cast(cast, fun) do
|
||||
{:noreply, fun.(cast)}
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
test "ensures default behaviour." do
|
||||
{:ok, bs} = BotStateStub.start_link()
|
||||
{:ok, bs} = BotStateStub.start_link()
|
||||
{:ok, pid} = TestPartA.start_link(bs, [])
|
||||
|
||||
s = :sys.get_state(pid)
|
||||
|
@ -65,12 +65,11 @@ defmodule Farmbot.BotState.Lib.PartitionTest do
|
|||
assert match?(^pub, s.public)
|
||||
assert s.bot_state_tracker == bs
|
||||
|
||||
fun = fn(cast) -> cast end
|
||||
fun = fn cast -> cast end
|
||||
|
||||
:ok = BotStateStub.put_test_fn(bs, fun)
|
||||
TestPartA.force_push(pid)
|
||||
res = BotStateStub.get_res(bs)
|
||||
assert match? {:update, TestPartA, ^pub}, res
|
||||
|
||||
assert match?({:update, TestPartA, ^pub}, res)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule Farmbot.BotState.LocationDataTest do
|
|||
alias LocationData.Vec3
|
||||
|
||||
setup do
|
||||
{:ok, bot_state_tracker} = BotState.start_link()
|
||||
{:ok, bot_state_tracker} = BotState.start_link()
|
||||
{:ok, location_data} = LocationData.start_link(bot_state_tracker, [])
|
||||
[location_data: location_data, bot_state_tracker: bot_state_tracker]
|
||||
end
|
||||
|
@ -20,26 +20,39 @@ defmodule Farmbot.BotState.LocationDataTest do
|
|||
test "updates position", ctx do
|
||||
LocationData.report_current_position(ctx.location_data, 1, 2, 3)
|
||||
assert :sys.get_state(ctx.location_data).public.position == %Vec3{x: 1, y: 2, z: 3}
|
||||
assert :sys.get_state(ctx.bot_state_tracker).bot_state.location_data.position == %Vec3{x: 1, y: 2, z: 3}
|
||||
|
||||
assert :sys.get_state(ctx.bot_state_tracker).bot_state.location_data.position == %Vec3{
|
||||
x: 1,
|
||||
y: 2,
|
||||
z: 3
|
||||
}
|
||||
end
|
||||
|
||||
test "updates scaled_encoders", ctx do
|
||||
LocationData.report_encoder_position_scaled(ctx.location_data, 1, 2, 3)
|
||||
assert :sys.get_state(ctx.location_data).public.scaled_encoders == %Vec3{x: 1, y: 2, z: 3}
|
||||
assert :sys.get_state(ctx.bot_state_tracker).bot_state.location_data.scaled_encoders == %Vec3{x: 1, y: 2, z: 3}
|
||||
|
||||
assert :sys.get_state(ctx.bot_state_tracker).bot_state.location_data.scaled_encoders == %Vec3{
|
||||
x: 1,
|
||||
y: 2,
|
||||
z: 3
|
||||
}
|
||||
end
|
||||
|
||||
test "updates raw_encoders", ctx do
|
||||
LocationData.report_encoder_position_raw(ctx.location_data, 1, 2, 3)
|
||||
assert :sys.get_state(ctx.location_data).public.raw_encoders == %Vec3{x: 1, y: 2, z: 3}
|
||||
assert :sys.get_state(ctx.bot_state_tracker).bot_state.location_data.raw_encoders == %Vec3{x: 1, y: 2, z: 3}
|
||||
|
||||
assert :sys.get_state(ctx.bot_state_tracker).bot_state.location_data.raw_encoders == %Vec3{
|
||||
x: 1,
|
||||
y: 2,
|
||||
z: 3
|
||||
}
|
||||
end
|
||||
|
||||
test "updates end_stops", ctx do
|
||||
LocationData.report_end_stops(ctx.location_data, 1,1,0,0,1,0)
|
||||
LocationData.report_end_stops(ctx.location_data, 1, 1, 0, 0, 1, 0)
|
||||
assert :sys.get_state(ctx.location_data).public.end_stops == "110010"
|
||||
assert :sys.get_state(ctx.bot_state_tracker).bot_state.location_data.end_stops == "110010"
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
|
@ -20,8 +20,9 @@ defmodule Farmbot.BotState.Transport.SupervisorTest do
|
|||
defmodule TestTransport do
|
||||
@moduledoc false
|
||||
use GenServer
|
||||
|
||||
def start_link(token, bot_state_tracker) do
|
||||
GenServer.start_link(__MODULE__, [token, bot_state_tracker], [name: __MODULE__])
|
||||
GenServer.start_link(__MODULE__, [token, bot_state_tracker], name: __MODULE__)
|
||||
end
|
||||
|
||||
def log(_), do: :ok
|
||||
|
|
|
@ -21,11 +21,10 @@ defmodule Farmbot.BotState.TransportTest do
|
|||
def log(_msg) do
|
||||
:ok
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
setup_all do
|
||||
{:ok, _tp} = StubTransport.start_link
|
||||
{:ok, _tp} = StubTransport.start_link()
|
||||
old = Application.get_env(:farmbot, :transport)
|
||||
new = old ++ [StubTransport]
|
||||
Application.put_env(:farmbot, :transport, new)
|
||||
|
@ -34,7 +33,7 @@ defmodule Farmbot.BotState.TransportTest do
|
|||
test "emits an ast" do
|
||||
msg = %Ast{kind: "hello", args: %{}, body: []}
|
||||
resp = Transport.emit(msg)
|
||||
Enum.map(resp, fn(res) -> assert res == :ok end)
|
||||
Enum.map(resp, fn res -> assert res == :ok end)
|
||||
end
|
||||
|
||||
test "logs a message" do
|
||||
|
@ -44,8 +43,8 @@ defmodule Farmbot.BotState.TransportTest do
|
|||
created_at: DateTime.utc_now(),
|
||||
channels: []
|
||||
}
|
||||
resp = Transport.log(log)
|
||||
Enum.map(resp, fn(res) -> assert res == :ok end)
|
||||
end
|
||||
|
||||
resp = Transport.log(log)
|
||||
Enum.map(resp, fn res -> assert res == :ok end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,10 +18,11 @@ defmodule Farmbot.BotStateTest do
|
|||
assert match?(:ok, first)
|
||||
assert match?({:error, :already_subscribed}, second)
|
||||
|
||||
task = Task.async(fn() ->
|
||||
third = BotState.subscribe(pid)
|
||||
assert match?(:ok, third)
|
||||
end)
|
||||
task =
|
||||
Task.async(fn ->
|
||||
third = BotState.subscribe(pid)
|
||||
assert match?(:ok, third)
|
||||
end)
|
||||
|
||||
Task.await(task)
|
||||
end
|
||||
|
@ -34,13 +35,10 @@ defmodule Farmbot.BotStateTest do
|
|||
assert unsub == true
|
||||
end
|
||||
|
||||
|
||||
test "updates parts" do
|
||||
alias BotState.{
|
||||
InformationalSettings, Configuration, LocationData, ProcessInfo
|
||||
}
|
||||
alias BotState.{InformationalSettings, Configuration, LocationData, ProcessInfo}
|
||||
|
||||
{:ok, pid} = BotState.start_link []
|
||||
{:ok, pid} = BotState.start_link([])
|
||||
:ok = GenServer.cast(pid, {:update, InformationalSettings, :info_state})
|
||||
:ok = GenServer.cast(pid, {:update, Configuration, :config_state})
|
||||
:ok = GenServer.cast(pid, {:update, LocationData, :location_data_state})
|
||||
|
|
|
@ -3,10 +3,11 @@ defmodule Farmbot.CeleryScript.AstTest do
|
|||
use ExUnit.Case
|
||||
|
||||
alias Farmbot.CeleryScript.Ast
|
||||
|
||||
|
||||
test "parses a string key'd map" do
|
||||
ast = %{"kind" => "kind", "args" => %{}}
|
||||
|> Ast.parse()
|
||||
ast =
|
||||
%{"kind" => "kind", "args" => %{}}
|
||||
|> Ast.parse()
|
||||
|
||||
assert ast.kind == "kind"
|
||||
assert ast.args == %{}
|
||||
|
@ -27,23 +28,25 @@ defmodule Farmbot.CeleryScript.AstTest do
|
|||
end
|
||||
|
||||
test "parses a struct" do
|
||||
ast = %SomeUnion{kind: "whooo", args: %{}} |> Ast.parse
|
||||
ast = %SomeUnion{kind: "whooo", args: %{}} |> Ast.parse()
|
||||
assert ast.kind == "whooo"
|
||||
assert ast.args == %{}
|
||||
assert ast.body == []
|
||||
end
|
||||
|
||||
test "raises when something doesn't match" do
|
||||
assert_raise Farmbot.CeleryScript.Error, "\"whoops this isn't right\" could not be parsed as CeleryScript.", fn() ->
|
||||
Ast.parse("whoops this isn't right")
|
||||
end
|
||||
assert_raise Farmbot.CeleryScript.Error,
|
||||
"\"whoops this isn't right\" could not be parsed as CeleryScript.",
|
||||
fn ->
|
||||
Ast.parse("whoops this isn't right")
|
||||
end
|
||||
end
|
||||
|
||||
test "parses body nodes" do
|
||||
sub_ast_1 = %{"kind" => "sub1", "args" => %{}}
|
||||
sub_ast_2 = %{kind: "sub2", args: %{}}
|
||||
ast = %{kind: "hey", args: %{}, body: [sub_ast_1, sub_ast_2]} |> Ast.parse()
|
||||
|
||||
|
||||
assert ast.kind == "hey"
|
||||
assert ast.args == %{}
|
||||
assert Enum.at(ast.body, 0).kind == "sub1"
|
||||
|
@ -69,5 +72,4 @@ defmodule Farmbot.CeleryScript.AstTest do
|
|||
assert ast.args == %{arg: 1}
|
||||
assert ast.body == []
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -27,8 +27,8 @@ defmodule Farmbot.CeleryScript.VirtualMachine.InstructionSetTest do
|
|||
|
||||
use ExUnit.Case
|
||||
alias Farmbot.CeleryScript.VirtualMachine.InstructionSet
|
||||
alias Farmbot.CeleryScript.VirtualMachine.UndefinedInstructionError
|
||||
|
||||
alias Farmbot.CeleryScript.VirtualMachine.UndefinedInstructionError
|
||||
|
||||
test "implements squarebracket access" do
|
||||
is = %InstructionSet{instructions: %{SomeInstr => SomeImpl}}
|
||||
assert is[SomeInstr] == SomeImpl
|
||||
|
@ -36,16 +36,17 @@ defmodule Farmbot.CeleryScript.VirtualMachine.InstructionSetTest do
|
|||
|
||||
test "squarebracket access raises if there is no implemetation" do
|
||||
is = %InstructionSet{}
|
||||
assert_raise UndefinedInstructionError,
|
||||
"Undefined instruction: some_cool_instr", fn() ->
|
||||
|
||||
assert_raise UndefinedInstructionError, "Undefined instruction: some_cool_instr", fn ->
|
||||
is[SomeCoolInstr]
|
||||
end
|
||||
end
|
||||
|
||||
test "builds new instruction sets" do
|
||||
is = InstructionSet.new()
|
||||
|> InstructionSet.impl(SomeInstr, SomeImpl)
|
||||
|> InstructionSet.impl(SomeOtherInstr, SomeOtherImpl)
|
||||
is =
|
||||
InstructionSet.new()
|
||||
|> InstructionSet.impl(SomeInstr, SomeImpl)
|
||||
|> InstructionSet.impl(SomeOtherInstr, SomeOtherImpl)
|
||||
|
||||
assert is[SomeInstr] == SomeImpl
|
||||
assert is[SomeOtherInstr] == SomeOtherImpl
|
||||
|
@ -53,9 +54,8 @@ defmodule Farmbot.CeleryScript.VirtualMachine.InstructionSetTest do
|
|||
|
||||
test "raises when trying to implement a instruction with no code." do
|
||||
is = InstructionSet.new()
|
||||
assert_raise CompileError,
|
||||
" Failed to load implementation: some_cool_impl.", fn() ->
|
||||
|
||||
|
||||
assert_raise CompileError, " Failed to load implementation: some_cool_impl.", fn ->
|
||||
is
|
||||
|> InstructionSet.impl(SomeCoolInstr, SomeCoolImpl)
|
||||
end
|
||||
|
@ -63,7 +63,8 @@ defmodule Farmbot.CeleryScript.VirtualMachine.InstructionSetTest do
|
|||
|
||||
test "raises when the module exists but does not implement CeleryScript." do
|
||||
is = InstructionSet.new()
|
||||
assert_raise CompileError, " half_impl does not implement CeleryScript.", fn() ->
|
||||
|
||||
assert_raise CompileError, " half_impl does not implement CeleryScript.", fn ->
|
||||
is |> InstructionSet.impl(SomeCoolInstr, HalfImpl)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,29 +1,31 @@
|
|||
defmodule Farmbot.CeleryScript.VirtualMachine.RuntimeErrorTest do
|
||||
@moduledoc "Tests runtime errors."
|
||||
|
||||
|
||||
use ExUnit.Case
|
||||
|
||||
alias Farmbot.CeleryScript.VirtualMachine
|
||||
alias VirtualMachine.RuntimeError, as: VmError
|
||||
|
||||
test "raises a runtime error" do
|
||||
assert_raise VmError, "runtime error", fn() ->
|
||||
raise VmError, machine: %VirtualMachine{},
|
||||
exception: %RuntimeError{}
|
||||
assert_raise VmError, "runtime error", fn ->
|
||||
raise VmError,
|
||||
machine: %VirtualMachine{},
|
||||
exception: %RuntimeError{}
|
||||
end
|
||||
|
||||
assert_raise VmError, "cool text", fn() ->
|
||||
raise VmError, machine: %VirtualMachine{},
|
||||
exception: %RuntimeError{message: "cool text"}
|
||||
assert_raise VmError, "cool text", fn ->
|
||||
raise VmError,
|
||||
machine: %VirtualMachine{},
|
||||
exception: %RuntimeError{message: "cool text"}
|
||||
end
|
||||
end
|
||||
|
||||
test "raises when there is no message or machine supplied" do
|
||||
assert_raise ArgumentError, "Machine state was not supplied to #{VmError}.", fn() ->
|
||||
assert_raise ArgumentError, "Machine state was not supplied to #{VmError}.", fn ->
|
||||
raise VmError, exception: %RuntimeError{}
|
||||
end
|
||||
|
||||
assert_raise ArgumentError, "Exception was not supplied to #{VmError}.", fn() ->
|
||||
assert_raise ArgumentError, "Exception was not supplied to #{VmError}.", fn ->
|
||||
raise VmError, machine: %VirtualMachine{}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ defmodule Farmbot.CeleryScript.VirtualMachine.StackFrameTest do
|
|||
use ExUnit.Case
|
||||
|
||||
alias Farmbot.CeleryScript.VirtualMachine.StackFrame
|
||||
|
||||
|
||||
test "creates a stack frame" do
|
||||
%StackFrame{body: [], args: %{}, return_address: 1234}
|
||||
end
|
||||
|
|
|
@ -6,10 +6,7 @@ defmodule Farmbot.VirtualMachineTest do
|
|||
|
||||
use ExUnit.Case
|
||||
|
||||
alias Farmbot.CeleryScript.{
|
||||
Ast,
|
||||
VirtualMachine
|
||||
}
|
||||
alias Farmbot.CeleryScript.{Ast, VirtualMachine}
|
||||
|
||||
# alias VirtualMachine.RuntimeError, as: VmError
|
||||
# alias VirtualMachine.InstructionSet
|
||||
|
@ -23,13 +20,14 @@ defmodule Farmbot.VirtualMachineTest do
|
|||
test "raises on unknown instruction" do
|
||||
kind = "do_a_barrel_roll"
|
||||
ast = build_ast(kind)
|
||||
assert_raise UndefinedInstructionError, "Undefined instruction: #{kind}", fn() ->
|
||||
|
||||
assert_raise UndefinedInstructionError, "Undefined instruction: #{kind}", fn ->
|
||||
VirtualMachine.step(%VirtualMachine{running: true, program: [ast]})
|
||||
end
|
||||
end
|
||||
|
||||
test "doesn't step when `running: false`" do
|
||||
vm = %VirtualMachine{running: false, program: []}
|
||||
vm = %VirtualMachine{running: false, program: []}
|
||||
new = VirtualMachine.step(vm)
|
||||
assert vm == new
|
||||
end
|
||||
|
|
|
@ -48,7 +48,7 @@ defmodule Farmbot.Firmware.Gcode.ParserTest do
|
|||
|
||||
test "parses report position" do
|
||||
a = Farmbot.Firmware.Gcode.Parser.parse_code("R82 X1 Y2 Z3 Q10")
|
||||
assert a == {"10", {:report_current_position, 1,2,3}}
|
||||
assert a == {"10", {:report_current_position, 1, 2, 3}}
|
||||
end
|
||||
|
||||
test "parses report calibration" do
|
||||
|
|
|
@ -15,7 +15,18 @@ defmodule Farmbot.FirmwareTest do
|
|||
setup do
|
||||
bss = Farmbot.BotStateSupport.start_bot_state_stack()
|
||||
mod = FirmwareHandler
|
||||
{:ok, firmware} = Firmware.start_link(bss.bot_state, bss.informational_settings, bss.configuration, bss.location_data, bss.mcu_params, mod, [])
|
||||
|
||||
{:ok, firmware} =
|
||||
Firmware.start_link(
|
||||
bss.bot_state,
|
||||
bss.informational_settings,
|
||||
bss.configuration,
|
||||
bss.location_data,
|
||||
bss.mcu_params,
|
||||
mod,
|
||||
[]
|
||||
)
|
||||
|
||||
{:ok, Map.put(bss, :firmware, firmware)}
|
||||
end
|
||||
|
||||
|
@ -29,23 +40,35 @@ defmodule Farmbot.FirmwareTest do
|
|||
assert :sys.get_state(ctx.bot_state).bot_state.informational_settings.busy == false
|
||||
end
|
||||
|
||||
test "reports position", ctx do
|
||||
Firmware.handle_gcode(ctx.firmware, {:report_current_position, 123, 1234, -123})
|
||||
assert match?(%{x: 123, y: 1234, z: -123}, :sys.get_state(ctx.bot_state).bot_state.location_data.position)
|
||||
end
|
||||
test "reports position", ctx do
|
||||
Firmware.handle_gcode(ctx.firmware, {:report_current_position, 123, 1234, -123})
|
||||
|
||||
test "reports scaled encoders", ctx do
|
||||
Firmware.handle_gcode(ctx.firmware, {:report_encoder_position_scaled, 123, 1234, -123})
|
||||
assert match?(%{x: 123, y: 1234, z: -123}, :sys.get_state(ctx.bot_state).bot_state.location_data.scaled_encoders)
|
||||
end
|
||||
assert match?(
|
||||
%{x: 123, y: 1234, z: -123},
|
||||
:sys.get_state(ctx.bot_state).bot_state.location_data.position
|
||||
)
|
||||
end
|
||||
|
||||
test "reports raw encoders", ctx do
|
||||
Firmware.handle_gcode(ctx.firmware, {:report_encoder_position_raw, 123, 1234, -123})
|
||||
assert match?(%{x: 123, y: 1234, z: -123}, :sys.get_state(ctx.bot_state).bot_state.location_data.raw_encoders)
|
||||
end
|
||||
test "reports scaled encoders", ctx do
|
||||
Firmware.handle_gcode(ctx.firmware, {:report_encoder_position_scaled, 123, 1234, -123})
|
||||
|
||||
test "reports end stops", ctx do
|
||||
Firmware.handle_gcode(ctx.firmware, {:report_end_stops, 1, 1, 0, 0, 1, 1})
|
||||
assert :sys.get_state(ctx.bot_state).bot_state.location_data.end_stops == "110011"
|
||||
end
|
||||
assert match?(
|
||||
%{x: 123, y: 1234, z: -123},
|
||||
:sys.get_state(ctx.bot_state).bot_state.location_data.scaled_encoders
|
||||
)
|
||||
end
|
||||
|
||||
test "reports raw encoders", ctx do
|
||||
Firmware.handle_gcode(ctx.firmware, {:report_encoder_position_raw, 123, 1234, -123})
|
||||
|
||||
assert match?(
|
||||
%{x: 123, y: 1234, z: -123},
|
||||
:sys.get_state(ctx.bot_state).bot_state.location_data.raw_encoders
|
||||
)
|
||||
end
|
||||
|
||||
test "reports end stops", ctx do
|
||||
Firmware.handle_gcode(ctx.firmware, {:report_end_stops, 1, 1, 0, 0, 1, 1})
|
||||
assert :sys.get_state(ctx.bot_state).bot_state.location_data.end_stops == "110011"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ defmodule Farmbot.HTTPTest do
|
|||
end
|
||||
|
||||
test "bang functions raise an error", %{http: http} do
|
||||
assert_raise HTTP.Error, fn() ->
|
||||
assert_raise HTTP.Error, fn ->
|
||||
HTTP.request!(http, :get, "https://some_not_real_url.blerp")
|
||||
end
|
||||
end
|
||||
|
@ -107,7 +107,7 @@ defmodule Farmbot.HTTPTest do
|
|||
|
||||
@tag :httpbin
|
||||
test "raises if the response isnt 2xx", %{http: http} do
|
||||
assert_raise HTTP.Error, fn() ->
|
||||
assert_raise HTTP.Error, fn ->
|
||||
HTTP.request!(http, :get, "https://httpbin.org/status/404")
|
||||
end
|
||||
end
|
||||
|
@ -117,5 +117,4 @@ defmodule Farmbot.HTTPTest do
|
|||
refute match?({:ok, _}, r)
|
||||
assert r.status_code == 200
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -8,6 +8,7 @@ defmodule Farmbot.JwtTest do
|
|||
doctest Farmbot.Jwt
|
||||
|
||||
alias Farmbot.Jwt
|
||||
|
||||
setup do
|
||||
[token: @token]
|
||||
end
|
||||
|
@ -19,7 +20,7 @@ defmodule Farmbot.JwtTest do
|
|||
{:ok, token} = r
|
||||
assert Jwt.decode!(tkn) == token
|
||||
assert token.bot == "device_2"
|
||||
assert token.exp == 1505883605
|
||||
assert token.exp == 1_505_883_605
|
||||
assert token.iss == "//192.168.29.204:3000"
|
||||
assert token.mqtt == "192.168.29.204"
|
||||
end
|
||||
|
@ -34,7 +35,7 @@ defmodule Farmbot.JwtTest do
|
|||
|
||||
test "Gives Poison Error when it can't be decoded as json", %{token: tkn} do
|
||||
[head, _body, foot] = String.split(tkn, ".")
|
||||
not_token = Base.encode64 "hello world", padding: :false
|
||||
not_token = Base.encode64("hello world", padding: false)
|
||||
tkn = [head, not_token, foot] |> Enum.join(".")
|
||||
r = Jwt.decode(tkn)
|
||||
refute match?({:ok, _}, r)
|
||||
|
@ -44,16 +45,18 @@ defmodule Farmbot.JwtTest do
|
|||
test "raises on bad token because base64", %{token: tkn} do
|
||||
[head, _body, foot] = String.split(tkn, ".")
|
||||
tkn = [head, "not_a_valid_token", foot] |> Enum.join(".")
|
||||
assert_raise RuntimeError, "Failed to base64 decode.", fn() ->
|
||||
|
||||
assert_raise RuntimeError, "Failed to base64 decode.", fn ->
|
||||
Jwt.decode!(tkn)
|
||||
end
|
||||
end
|
||||
|
||||
test "Raises on bad json.", %{token: tkn} do
|
||||
[head, _body, foot] = String.split(tkn, ".")
|
||||
not_token = Base.encode64 "hello world", padding: :false
|
||||
not_token = Base.encode64("hello world", padding: false)
|
||||
tkn = [head, not_token, foot] |> Enum.join(".")
|
||||
assert_raise RuntimeError, "Failed to json decode.", fn() ->
|
||||
|
||||
assert_raise RuntimeError, "Failed to json decode.", fn ->
|
||||
Jwt.decode!(tkn)
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue