use Elixir 1.6.0 code formatter

pull/363/head
Connor Rigby 2017-10-11 15:53:00 -07:00
parent 3999ef58d9
commit fe32cd1540
109 changed files with 1432 additions and 1004 deletions

Binary file not shown.

Binary file not shown.

View File

@ -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

View File

@ -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"
]

View File

@ -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"

View File

@ -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,

View File

@ -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,

View File

@ -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],

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,4 +1,3 @@
defmodule Farmbot.Firmware.UartHandler.AutoDetector do
@moduledoc "Helper for autodetecting and configuring a UART fw device."
end

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 []

View File

@ -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]

View File

@ -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 []

View File

@ -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 []

View File

@ -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]

View File

@ -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 []

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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
"""

View File

@ -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
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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})

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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