Merge pull request #1173 from FarmBot/staging

Publish v9.2.1
pull/1186/head v9.2.1
Rick Carlino 2020-03-24 14:25:49 -05:00 committed by GitHub
commit 1ab623a2b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 482 additions and 416 deletions

View File

@ -1,5 +1,12 @@
# Changelog
# 9.2.1
* Improve firmware debug messages.
* Remove confusing firmware debug messages, such as "Error OK".
* Improved camera support on FarmBot express.
* Bug fix to prevents OTA updates occuring when one is already in progress.
# 9.2.0
* Support for criteria-based groups.

10
COVERAGE.md 100644
View File

@ -0,0 +1,10 @@
# Jan - Mar 2020
| Project | Jan 1 20 | Feb 6 20 | Mar 4 20 |STATUS|
|-----------------------|----------|----------|----------|------|
| farmbot_celery_script | 53.7% | 54.0% | 54.0% |OK |
| farmbot_core | 22.2% | 19.8% | 26.3% |OK |
| farmbot_ext | 53.6% | 52.7% | 38.1% |FIX | !!!
| farmbot_firmware | 13.8% | 56.4% | 62.0% |OK |
| farmbot_os | 22.0% | 27.6% | 45.3% |OK |
| farmbot_telemetry | ??.?% | ??.?% | ??.?% |LATER |

View File

@ -1 +1 @@
9.2.0
9.2.1

View File

@ -189,7 +189,6 @@ defmodule FarmbotCore.Asset.Command do
# Catch-all use case:
def update(asset_kind, id, params) do
Logger.warn("AssetCommand needs implementation: #{asset_kind}")
mod = as_module!(asset_kind)
case Repo.get_by(mod, id: id) do

View File

@ -94,7 +94,6 @@ defmodule FarmbotCore.AssetMonitor do
Map.put(sub_state, id, updated_at)
is_nil(sub_state[id]) ->
Logger.debug("#{inspect(kind)} #{id} needs to be started")
asset = Repo.preload(asset, AssetWorker.preload(asset))
:ok = AssetSupervisor.start_child(asset) |> assert_result!(asset)
Map.put(sub_state, id, updated_at)

View File

@ -1,6 +1,8 @@
defmodule FarmbotCore.BotState do
@moduledoc "Central State accumulator."
alias FarmbotCore.BotStateNG
alias FarmbotCore.BotState.JobProgress.Percent
require FarmbotCore.Logger
use GenServer
@ -28,7 +30,7 @@ defmodule FarmbotCore.BotState do
def set_position(bot_state_server \\ __MODULE__, x, y, z) do
GenServer.call(bot_state_server, {:set_position, x, y, z})
end
@doc "Sets the location_data.load"
def set_load(bot_state_server \\ __MODULE__, x, y, z) do
GenServer.call(bot_state_server, {:set_load, x, y, z})
@ -161,6 +163,10 @@ defmodule FarmbotCore.BotState do
GenServer.call(bot_state_server, :enter_maintenance_mode)
end
def job_in_progress?(job_name, bot_state_server \\ __MODULE__) do
GenServer.call(bot_state_server, {:job_in_progress?, job_name})
end
@doc false
def start_link(args, opts \\ [name: __MODULE__]) do
GenServer.start_link(__MODULE__, args, opts)
@ -175,6 +181,13 @@ defmodule FarmbotCore.BotState do
FarmbotCore.Logger.error 1, "BotState crashed! #{inspect(reason)}"
end
def handle_call({:job_in_progress?, job_name}, _from, state) do
progress = (state.tree.jobs[job_name] || %Percent{}).percent
in_progress? = (progress > 0.0 && progress < 100.0)
{:reply, in_progress?, state}
end
@doc false
def handle_call(:subscribe, {pid, _} = _from, state) do
# TODO Just replace this with Elixir.Registry?

View File

@ -90,8 +90,8 @@ defmodule FarmbotCore.FirmwareOpenTask do
Config.update_config_value(:bool, "settings", "firmware_needs_open", false)
timer = Process.send_after(self(), :open, 5000)
{:noreply, %{state | timer: timer, attempts: 0}}
_ ->
FarmbotCore.Logger.debug 3, "Firmware failed to open"
other ->
FarmbotCore.Logger.debug 3, "Firmware failed to open: #{inspect(other)}"
timer = Process.send_after(self(), :open, 5000)
{:noreply, %{state | timer: timer, attempts: 0}}
end

View File

@ -193,7 +193,14 @@ defmodule FarmbotCore.FirmwareSideEffects do
def handle_debug_message([message]) do
fbos_config = Asset.fbos_config()
should_log? = fbos_config.firmware_debug_log || fbos_config.arduino_debug_messages
should_log? && FarmbotCore.Logger.debug(3, "Firmware debug message: " <> message)
should_log? && do_send_debug_message(message)
end
# TODO(Rick): 0 means OK, but firmware debug logs say "error 0". Why?
def do_send_debug_message("error 0"), do: do_send_debug_message("OK")
def do_send_debug_message(message) do
FarmbotCore.Logger.debug(3, "Firmware debug message: " <> message)
end
@impl FarmbotFirmware.SideEffects

View File

@ -13,7 +13,8 @@ defmodule FarmbotCore.Leds.StubHandler do
def white5(status), do: do_debug(:white, status)
defp do_debug(color, status) do
msg = [IO.ANSI.reset(), "LED STATUS: ",
unless System.get_env("LOG_SILENCE") do
msg = [IO.ANSI.reset(), "LED STATUS: ",
apply(IO.ANSI, color, []),
status_in(status),
to_string(color),
@ -22,7 +23,8 @@ defmodule FarmbotCore.Leds.StubHandler do
status_out(status),
IO.ANSI.reset()
]
IO.puts(msg)
IO.puts(msg)
end
end
defp status_in(:slow_blink), do: IO.ANSI.blink_slow()

View File

@ -23,7 +23,9 @@ defmodule FarmbotCore.LogExecutor do
do: level,
else: :info
Elixir.Logger.bare_log(logger_level, log, logger_meta)
unless System.get_env("LOG_SILENCE") do
Elixir.Logger.bare_log(logger_level, log, logger_meta)
end
log
end
end

View File

@ -23,3 +23,4 @@ erl_crash.dump
farmbot_ext-*.tar
*.sqlite3
*.coverdata

View File

@ -11,3 +11,4 @@ config :farmbot_celery_script, FarmbotCeleryScript.SysCalls,
import_config "ecto.exs"
import_config "farmbot_core.exs"
import_config "lagger.exs"
import_config "test.exs"

View File

@ -0,0 +1,5 @@
use Mix.Config
if Mix.env() == :test do
config :farmbot_ext, FarmbotExt, children: []
end

View File

@ -0,0 +1,6 @@
{
"coverage_options": {
"treat_no_relevant_lines_as_covered": true,
"minimum_coverage": 6.5
}
}

View File

@ -1,19 +1,18 @@
defmodule FarmbotExt do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
def start(_type, _args) do
# List all child processes to be supervised
children = [
FarmbotExt.Bootstrap
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: __MODULE__]
Supervisor.start_link(children, opts)
Supervisor.start_link(children(), opts)
end
# This only exists because I was getting too many crashed
# supervisor reports in the test suite (distraction from
# real test failures).
def children do
config = Application.get_env(:farmbot_ext, __MODULE__) || []
Keyword.get(config, :children, [FarmbotExt.Bootstrap])
end
end

View File

@ -0,0 +1,46 @@
defmodule FarmbotExt.AMQP.AutoSyncAssetHandler do
require Logger
alias FarmbotCore.{Asset, BotState, Leds}
alias FarmbotExt.API.{EagerLoader}
# Sync messgages about these assets
# should not be cached. They need to be applied
# in real time.
@no_cache_kinds ~w(
Device
FbosConfig
FirmwareConfig
FarmwareEnv
FarmwareInstallation
)
def handle_asset(asset_kind, id, params) do
if Asset.Query.auto_sync?() do
:ok = BotState.set_sync_status("syncing")
_ = Leds.green(:really_fast_blink)
Asset.Command.update(asset_kind, id, params)
:ok = BotState.set_sync_status("synced")
_ = Leds.green(:solid)
else
cache_sync(asset_kind, id, params)
end
end
def cache_sync(kind, id, params) when kind in @no_cache_kinds do
:ok = Asset.Command.update(kind, id, params)
end
def cache_sync(_, _, nil) do
:ok = BotState.set_sync_status("sync_now")
_ = Leds.green(:slow_blink)
end
def cache_sync(asset_kind, id, params) do
Logger.info("Autocaching sync #{asset_kind} #{id} #{inspect(params)}")
changeset = Asset.Command.new_changeset(asset_kind, id, params)
:ok = EagerLoader.cache(changeset)
:ok = BotState.set_sync_status("sync_now")
_ = Leds.green(:slow_blink)
end
end

View File

@ -8,14 +8,13 @@ defmodule FarmbotExt.AMQP.AutoSyncChannel do
use GenServer
use AMQP
alias FarmbotCore.{Asset, BotState, JSON, Leds}
alias FarmbotExt.AMQP.ConnectionWorker
alias FarmbotExt.API.{EagerLoader, Preloader}
require Logger
require FarmbotCore.Logger
require FarmbotTelemetry
alias FarmbotCore.{Asset, BotState, JSON, Leds}
alias FarmbotExt.AMQP.{ConnectionWorker, AutoSyncAssetHandler}
alias FarmbotExt.API.{EagerLoader, Preloader}
# The API dispatches messages for other resources, but these
# are the only ones that Farmbot needs to sync.
@known_kinds ~w(
@ -35,18 +34,8 @@ defmodule FarmbotExt.AMQP.AutoSyncChannel do
Tool
)
# Sync messgaes about these assets
# should not be cached. They need to be applied
# in real time.
@no_cache_kinds ~w(
Device
FbosConfig
FirmwareConfig
FarmwareEnv
FarmwareInstallation
)
defstruct [:conn, :chan, :jwt, :preloaded]
alias __MODULE__, as: State
@doc "Gets status of auto_sync connection for diagnostics / tests."
@ -68,12 +57,15 @@ defmodule FarmbotExt.AMQP.AutoSyncChannel do
def terminate(reason, state) do
FarmbotCore.Logger.error(1, "Disconnected from AutoSync channel: #{inspect(reason)}")
# If a channel was still open, close it.
if state.chan, do: ConnectionWorker.close_channel(state.chan)
if state.chan do
ConnectionWorker.close_channel(state.chan)
end
try do
EagerLoader.Supervisor.drop_all_cache()
catch
_, _ -> :ok
_, _ ->
:ok
end
end
@ -103,6 +95,7 @@ defmodule FarmbotExt.AMQP.AutoSyncChannel do
end
def handle_info(:connect, state) do
# THIS IS WHERE state.chan GETS SET
result = ConnectionWorker.maybe_connect_autosync(state.jwt.bot)
compute_reply_from_amqp_state(state, result)
end
@ -134,13 +127,11 @@ defmodule FarmbotExt.AMQP.AutoSyncChannel do
case String.split(key, ".") do
["bot", ^device, "sync", asset_kind, id_str] when asset_kind in @known_kinds ->
id = data["id"] || String.to_integer(id_str)
_ = handle_asset(asset_kind, id, body)
["bot", ^device, "sync", asset_kind, _id_str] ->
Logger.warn("Unknown syncable asset: #{asset_kind}")
_ = AutoSyncAssetHandler.handle_asset(asset_kind, id, body)
_ ->
Logger.info("ignoring route: #{key}")
""
# Logger.info("ignoring route: #{key}")
end
:ok = ConnectionWorker.rpc_reply(chan, device, label)
@ -158,36 +149,6 @@ defmodule FarmbotExt.AMQP.AutoSyncChannel do
{:reply, reply, state}
end
def handle_asset(asset_kind, id, params) do
if Asset.Query.auto_sync?() do
:ok = BotState.set_sync_status("syncing")
_ = Leds.green(:really_fast_blink)
# Logger.info "Syncing #{asset_kind} #{id} #{inspect(params)}"
Asset.Command.update(asset_kind, id, params)
:ok = BotState.set_sync_status("synced")
_ = Leds.green(:solid)
else
cache_sync(asset_kind, id, params)
end
end
def cache_sync(kind, id, params) when kind in @no_cache_kinds do
:ok = Asset.Command.update(kind, id, params)
end
def cache_sync(_, _, nil) do
:ok = BotState.set_sync_status("sync_now")
_ = Leds.green(:slow_blink)
end
def cache_sync(asset_kind, id, params) do
Logger.info("Autocaching sync #{asset_kind} #{id} #{inspect(params)}")
changeset = Asset.Command.new_changeset(asset_kind, id, params)
:ok = EagerLoader.cache(changeset)
:ok = BotState.set_sync_status("sync_now")
_ = Leds.green(:slow_blink)
end
defp compute_reply_from_amqp_state(state, %{conn: conn, chan: chan}) do
{:noreply, %{state | conn: conn, chan: chan}}
end

View File

@ -66,7 +66,6 @@ defmodule FarmbotExt.API.EagerLoader do
* a remote `id` field.
"""
def cache(%Changeset{data: %module{}} = changeset) do
Logger.info("Caching #{inspect(changeset)}")
id = Changeset.get_field(changeset, :id)
updated_at = Changeset.get_field(changeset, :updated_at)
id || change_error(changeset, "Can't cache a changeset with no :id attribute")

View File

@ -24,7 +24,7 @@ defmodule FarmbotExt.API.Preloader do
with {:ok, sync_changeset} <- API.get_changeset(Sync),
sync_changeset <- Reconciler.sync_group(sync_changeset, SyncGroup.group_0()) do
FarmbotCore.Logger.success(3, "Successfully preloaded resources.")
maybe_auto_sync(sync_changeset, Query.auto_sync?())
maybe_auto_sync(sync_changeset, Query.auto_sync?() || false)
end
end

View File

@ -51,8 +51,6 @@ defmodule FarmbotExt.Bootstrap do
end
def try_auth(email, server, password, _secret) do
Logger.debug("using password to auth")
with {:ok, tkn} <- Authorization.authorize_with_password(email, password, server),
_ <- update_config_value(:string, "authorization", "token", tkn),
{:ok, pid} <- Supervisor.start_child(FarmbotExt, Bootstrap.Supervisor) do

View File

@ -1,6 +1,6 @@
defmodule FarmbotExt.Bootstrap.Supervisor do
@moduledoc """
Supervisor responsible for starting all
Supervisor responsible for starting all
the tasks and processes that require authentication.
"""
use Supervisor
@ -20,7 +20,6 @@ defmodule FarmbotExt.Bootstrap.Supervisor do
FarmbotExt.Bootstrap.DropPasswordTask
]
opts = [strategy: :one_for_one]
Supervisor.init(children, opts)
Supervisor.init(children, strategy: :one_for_one)
end
end

View File

@ -0,0 +1,25 @@
defmodule AutoSyncAssetHandlerTest do
use ExUnit.Case, async: true
use Mimic
setup :verify_on_exit!
setup :set_mimic_global
alias FarmbotExt.AMQP.AutoSyncAssetHandler
alias FarmbotCore.{Asset, BotState, Leds}
def auto_sync_off, do: expect(Asset.Query, :auto_sync?, fn -> false end)
def expect_sync_status_to_be(status),
do: expect(BotState, :set_sync_status, fn ^status -> :ok end)
def expect_green_leds(status),
do: expect(Leds, :green, 1, fn ^status -> :ok end)
test "handling of deleted assets when auto_sync is disabled" do
auto_sync_off()
expect_sync_status_to_be("sync_now")
expect_green_leds(:slow_blink)
AutoSyncAssetHandler.handle_asset("Point", 23, nil)
end
end

View File

@ -1,18 +1,15 @@
defmodule AutoSyncChannelTest do
require Helpers
use ExUnit.Case, async: true
use Mimic
alias FarmbotExt.AMQP.AutoSyncChannel
use ExUnit.Case
use Mimic
alias FarmbotCore.JSON
alias FarmbotCore.Asset.{
Query,
Command,
Sync
alias FarmbotExt.{
AMQP.ConnectionWorker,
API.Preloader,
JWT
}
alias FarmbotExt.{JWT, API, AMQP.ConnectionWorker}
setup :verify_on_exit!
setup :set_mimic_global
@ -35,203 +32,127 @@ defmodule AutoSyncChannelTest do
"eXTEVkqw7rved84ogw6EKBSFCVqwRA-NKWLpPMV_q7fRwiEG" <>
"Wj7R-KZqRweALXuvCLF765E6-ENxA"
def pretend_network_returned(fake_value) do
def generate_pid do
apply_default_mocks()
jwt = JWT.decode!(@fake_jwt)
test_pid = self()
expect(Query, :auto_sync?, 2, fn -> false end)
expect(API, :get_changeset, fn _module ->
send(test_pid, :preload_all_called)
changeset = Sync.changeset(%Sync{}, %{})
{:ok, changeset}
end)
expect(ConnectionWorker, :maybe_connect_autosync, fn jwt ->
send(test_pid, {:maybe_connect_called, jwt})
fake_value
end)
stub(ConnectionWorker, :close_channel, fn _ ->
send(test_pid, :close_channel_called)
:ok
end)
stub(ConnectionWorker, :rpc_reply, fn chan, jwt_dot_bot, label ->
send(test_pid, {:rpc_reply_called, chan, jwt_dot_bot, label})
:ok
end)
{:ok, pid} = AutoSyncChannel.start_link([jwt: jwt], [])
Map.merge(%{pid: pid}, AutoSyncChannel.network_status(pid))
pid
end
def under_normal_conditions() do
fake_con = %{fake: :conn}
fake_chan = %{fake: :chan}
pretend_network_returned(%{conn: fake_con, chan: fake_chan})
def apply_default_mocks do
ok1 = fn _ -> :whatever end
stub(FarmbotExt.API.EagerLoader.Supervisor, :drop_all_cache, fn -> :ok end)
stub(ConnectionWorker, :close_channel, ok1)
stub(ConnectionWorker, :maybe_connect_autosync, fn _ ->
%{conn: %{fake_conn: true}, chan: %{fake_chan: true}}
end)
end
test "network returns `nil`" do
results = pretend_network_returned(nil)
%{conn: has_conn, chan: has_chan, preloaded: is_preloaded} = results
assert has_chan == nil
assert has_conn == nil
assert is_preloaded
def ensure_response_to(msg) do
# Not much to check here other than matching clauses.
# AMQP lib handles most all of this.
expect(Preloader, :preload_all, 1, fn -> :ok end)
pid = generate_pid()
send(pid, msg)
Process.sleep(5)
end
test "network returns unexpected object (probably an error)" do
results = pretend_network_returned({:something, :else})
%{conn: has_conn, chan: has_chan, preloaded: is_preloaded} = results
test "basic_cancel", do: ensure_response_to({:basic_cancel, :anything})
test "basic_cancel_ok", do: ensure_response_to({:basic_cancel_ok, :anything})
test "basic_consume_ok", do: ensure_response_to({:basic_consume_ok, :anything})
assert has_chan == nil
assert has_conn == nil
assert is_preloaded
test "init / terminate - auto_sync enabled" do
expect(Preloader, :preload_all, 1, fn -> :ok end)
expect(FarmbotCore.Asset.Query, :auto_sync?, 1, fn -> true end)
expect(FarmbotCore.BotState, :set_sync_status, 1, fn "synced" -> :ok end)
expect(FarmbotCore.Leds, :green, 2, fn
:solid ->
:ok
:really_fast_blink ->
:ok
end)
# Helpers.expect_log("Failed to connect to AutoSync channel: :whatever")
# Helpers.expect_log("Disconnected from AutoSync channel: :normal")
pid = generate_pid()
assert %{chan: nil, conn: nil, preloaded: true} == AutoSyncChannel.network_status(pid)
GenServer.stop(pid, :normal)
end
test "catch-all clause for inbound AMQP messages" do
fake_con = %{fake: :conn}
fake_chan = %{fake: :chan}
fake_response = %{conn: fake_con, chan: fake_chan}
test "init / terminate - auto_sync disabled" do
expect(Preloader, :preload_all, 1, fn -> :ok end)
expect(FarmbotCore.Asset.Query, :auto_sync?, 1, fn -> false end)
expect(FarmbotCore.BotState, :set_sync_status, 1, fn "sync_now" -> :ok end)
%{pid: pid} = pretend_network_returned(fake_response)
expect(FarmbotCore.Leds, :green, 2, fn
:slow_blink ->
:ok
payload =
JSON.encode!(%{
args: %{label: "xyz"}
:really_fast_blink ->
:ok
end)
Helpers.expect_log("Disconnected from AutoSync channel: :normal")
pid = generate_pid()
assert %{chan: nil, conn: nil, preloaded: true} == AutoSyncChannel.network_status(pid)
GenServer.stop(pid, :normal)
end
test "init / terminate - auto_sync error" do
Helpers.expect_log("Error preloading. #{inspect("a test example")}")
Helpers.expect_log("Disconnected from AutoSync channel: :normal")
expect(FarmbotCore.BotState, :set_sync_status, 1, fn "sync_error" -> :ok end)
expect(Preloader, :preload_all, 1, fn -> {:error, "a test example"} end)
expect(FarmbotCore.Leds, :green, 2, fn
:slow_blink ->
:ok
:really_fast_blink ->
:ok
end)
pid = generate_pid()
assert %{chan: nil, conn: nil, preloaded: false} == AutoSyncChannel.network_status(pid)
GenServer.stop(pid, :normal)
end
test "delivery of auto sync messages" do
expect(Preloader, :preload_all, 1, fn -> :ok end)
expect(ConnectionWorker, :rpc_reply, 1, fn chan, device, label ->
assert chan == %{fake_chan: true}
assert device == "device_15"
assert label == "thisismylabelinatestsuite"
:ok
end)
key = "bot.device_15.sync.Device.46"
{:ok, payload} =
FarmbotCore.JSON.encode(%{
"id" => 46,
"args" => %{
"label" => "thisismylabelinatestsuite"
},
"body" => %{name: "This is my bot"}
})
send(pid, {:basic_deliver, payload, %{routing_key: "WRONG!"}})
assert_receive {:rpc_reply_called, %{fake: :chan}, "device_15", "xyz"}
end
test "wont autosync unknown assets" do
fake_con = %{fake: :conn}
fake_chan = %{fake: :chan}
fake_response = %{conn: fake_con, chan: fake_chan}
%{pid: pid} = pretend_network_returned(fake_response)
payload =
JSON.encode!(%{
args: %{label: "xyz"}
})
send(pid, {:basic_deliver, payload, %{routing_key: "bot.device_15.sync.SavedGarden.999"}})
assert_receive {:rpc_reply_called, %{fake: :chan}, "device_15", "xyz"}
end
test "ignores asset deletion when auto_sync is off" do
%{pid: pid} = under_normal_conditions()
test_pid = self()
payload = '{"args":{"label":"foo"}}'
key = "bot.device_15.sync.Device.999"
stub(Query, :auto_sync?, fn ->
send(test_pid, :called_auto_sync?)
false
end)
send(pid, {:basic_deliver, payload, %{routing_key: key}})
assert_receive :called_auto_sync?
end
test "handles Device assets" do
%{pid: pid} = under_normal_conditions()
test_pid = self()
payload = '{"args":{"label":"foo"},"body":{}}'
key = "bot.device_15.sync.Device.999"
stub(Query, :auto_sync?, fn -> true end)
stub(Command, :update, fn x, y, z ->
send(test_pid, {:update_called, x, y, z})
:ok
end)
send(pid, {:basic_deliver, payload, %{routing_key: key}})
assert_receive {:update_called, "Device", 999, %{}}
end
def simple_asset_test_singleton(module_name) do
%{pid: pid} = under_normal_conditions()
test_pid = self()
payload = '{"args":{"label":"foo"},"body":{"foo": "bar"}}'
key = "bot.device_15.sync.#{module_name}.999"
stub(Query, :auto_sync?, fn -> true end)
stub(Command, :update, fn x, y, z ->
send(test_pid, {:update_called, x, y, z})
:ok
end)
stub(Command, :update, fn x, y, z ->
send(test_pid, {:update_called, x, y, z})
:ok
end)
pid = generate_pid()
# We need the process to be preloaded for these tests to work:
%{preloaded: true} = AutoSyncChannel.network_status(pid)
send(pid, {:basic_deliver, payload, %{routing_key: key}})
assert_receive {:update_called, ^module_name, 999, %{"foo" => "bar"}}
end
test "handles auto_sync of 'no_cache' when auto_sync is false" do
test_pid = self()
%{pid: pid} = under_normal_conditions()
key = "bot.device_15.sync.FbosConfig.999"
payload = '{"args":{"label":"foo"},"body":{"foo": "bar"}}'
stub(Query, :auto_sync?, fn ->
send(test_pid, :called_auto_sync?)
false
end)
stub(Command, :update, fn kind, id, params ->
send(test_pid, {:update_called, kind, id, params})
expect(FarmbotExt.AMQP.AutoSyncAssetHandler, :handle_asset, fn kind, id, body ->
assert kind == "Device"
assert id == 46
assert body == %{"name" => "This is my bot"}
:ok
end)
send(pid, {:basic_deliver, payload, %{routing_key: key}})
assert_receive :called_auto_sync?
assert_receive {:update_called, "FbosConfig", 999, %{"foo" => "bar"}}
end
test "auto_sync disabled, resource not in @cache_kinds" do
under_normal_conditions()
stub(Query, :auto_sync?, fn ->
false
end)
stub(Command, :new_changeset, fn _kind, _id, _params ->
:ok
end)
end
test "handles FbosConfig", do: simple_asset_test_singleton("FbosConfig")
test "handles FirmwareConfig", do: simple_asset_test_singleton("FirmwareConfig")
test "handles FarmwareEnv", do: simple_asset_test_plural("FarmwareEnv")
test "handles FarmwareInstallation", do: simple_asset_test_plural("FarmwareInstallation")
defp simple_asset_test_plural(module_name) do
%{pid: pid} = under_normal_conditions()
test_pid = self()
payload = '{"args":{"label":"foo"},"body":{"foo": "bar"}}'
key = "bot.device_15.sync.#{module_name}.999"
stub(Query, :auto_sync?, fn -> true end)
stub(Command, :update, fn x, y, z ->
send(test_pid, {:update_called, x, y, z})
:ok
end)
send(pid, {:basic_deliver, payload, %{routing_key: key}})
assert_receive {:update_called, ^module_name, 999, %{"foo" => "bar"}}
Process.sleep(1000)
end
end

View File

@ -0,0 +1,25 @@
defmodule FarmbotExt.AMQP.BotStateChannelTest do
use ExUnit.Case
use Mimic
# alias FarmbotExt.AMQP.BotStateChannel
# alias FarmbotCore.BotState
setup :verify_on_exit!
setup :set_mimic_global
defmodule FakeState do
defstruct conn: %{fake: :conn}, chan: "fake_chan_", jwt: "fake_jwt_", cache: %{fake: :cache}
end
test "terminate" do
expected = "Disconnected from BotState channel: \"foo\""
expect(AMQP.Channel, :close, 1, fn "fake_chan_" -> :ok end)
expect(FarmbotCore.LogExecutor, :execute, 1, fn log ->
assert log.message == expected
end)
FarmbotExt.AMQP.BotStateChannel.terminate("foo", %FakeState{})
end
end

View File

@ -0,0 +1,12 @@
defmodule FarmbotExt.API.ViewTest do
use ExUnit.Case
def render(%{ok: :ok}) do
:yep
end
test "render/2" do
result = FarmbotExt.API.View.render(__MODULE__, %{ok: :ok})
assert :yep == result
end
end

View File

@ -1,14 +1,33 @@
Application.ensure_all_started(:farmbot)
timeout = System.get_env("EXUNIT_TIMEOUT")
Mimic.copy(AMQP.Channel)
Mimic.copy(FarmbotCeleryScript.SysCalls.Stubs)
Mimic.copy(FarmbotCore.Asset.Command)
Mimic.copy(FarmbotCore.Asset.Query)
Mimic.copy(FarmbotCore.BotState)
Mimic.copy(FarmbotCore.Leds)
Mimic.copy(FarmbotCore.LogExecutor)
Mimic.copy(FarmbotExt.AMQP.ConnectionWorker)
Mimic.copy(FarmbotExt.API.EagerLoader.Supervisor)
Mimic.copy(FarmbotExt.API.Preloader)
Mimic.copy(FarmbotExt.API)
Mimic.copy(FarmbotExt.AMQP.AutoSyncAssetHandler)
timeout = System.get_env("EXUNIT_TIMEOUT")
System.put_env("LOG_SILENCE", "true")
if timeout do
ExUnit.start(assert_receive_timeout: String.to_integer(timeout))
else
ExUnit.start()
end
defmodule Helpers do
defmacro expect_log(message) do
quote do
expect(FarmbotCore.LogExecutor, :execute, fn log ->
assert log.message == unquote(message)
end)
end
end
end

View File

@ -300,20 +300,10 @@ defmodule FarmbotFirmware do
name: state.reset
) do
{:ok, pid} ->
Logger.debug(
"Firmware reset #{state.reset} started. #{
inspect(state.transport_args)
}"
)
{:noreply, %{state | reset_pid: pid}}
# TODO(Rick): I have no idea what's going on here.
{:error, {:already_started, pid}} ->
Logger.debug(
"Firmware reset complete. #{inspect(state.transport_args)}"
)
{:noreply, %{state | reset_pid: pid}}
error ->

View File

@ -4,7 +4,7 @@ defmodule FarmbotOS.SysCalls.Farmware do
require FarmbotCore.Logger
alias FarmbotCore.{Asset, AssetSupervisor, FarmwareRuntime}
alias FarmbotExt.API.ImageUploader
@farmware_timeout 30_000
@farmware_timeout 60_000
def update_farmware(farmware_name) do
with {:ok, installation} <- lookup_installation(farmware_name) do

View File

@ -41,8 +41,8 @@ defmodule FarmbotOS.SysCalls.FlashFirmware do
{:error, reason} when is_binary(reason) ->
{:error, reason}
{_, exit_code} when is_number(exit_code) ->
{:error, "avrdude error: #{exit_code}"}
error ->
{:error, "flash_firmware misc error: #{inspect(error)}"}
end
end

View File

@ -95,6 +95,9 @@ defmodule FarmbotOS.SysCalls.Movement do
end
end
# TODO(Rick): Figure out source of Error: {:ok, "ok"} logs.
def handle_movement_error({:ok, _}), do: :ok
def handle_movement_error(reason) do
msg = "Movement failed. #{inspect(reason)}"
FarmbotCore.Logger.error(1, msg)

View File

@ -126,7 +126,7 @@ defmodule FarmbotOS.MixProject do
{:busybox, "~> 0.1.4", targets: @all_targets},
{:farmbot_system_rpi3, "1.10.0-farmbot.1",
runtime: false, targets: :rpi3},
{:farmbot_system_rpi, "1.10.0-farmbot.1", runtime: false, targets: :rpi}
{:farmbot_system_rpi, "1.10.0-farmbot.2", runtime: false, targets: :rpi}
]
end

View File

@ -1,98 +1,98 @@
%{
"amqp": {:hex, :amqp, "1.4.0", "4172595d467b9360850a8eca254c5946af9970684d335d555a9f3410a0e43995", [:mix], [{:amqp_client, "~> 3.8.0", [hex: :amqp_client, repo: "hexpm", optional: false]}], "hexpm"},
"amqp_client": {:hex, :amqp_client, "3.8.2", "b50ac381c3c016a697d6ab8f08367043a08358cfeb8ee97832ccc7d101e59cef", [:make, :rebar3], [{:rabbit_common, "3.8.2", [hex: :rabbit_common, repo: "hexpm", optional: false]}], "hexpm"},
"busybox": {:hex, :busybox, "0.1.4", "9b07860c0663e7d0ace3093100ede44860bd73c22b17c2941a4b17e25893cc36", [:make, :mix], [{:elixir_make, "~> 0.5", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"circuits_gpio": {:hex, :circuits_gpio, "0.4.3", "1a53dff1eaeefb9f67f4ebc2c1852b603683eedaa6053bed51c038dd64b978bb", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"circuits_i2c": {:hex, :circuits_i2c, "0.3.5", "43e043d7efc3aead364061f8a7ed627f81ff7cef52bfa47cb629d8a68ca56a9f", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"circuits_uart": {:hex, :circuits_uart, "1.4.0", "799abad2d5f355bd571c46de089e62c6341e6b08f9fdf51f4d53d50f5d5bbda9", [:mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
"cors_plug": {:hex, :cors_plug, "2.0.0", "238ddb479f92b38f6dc1ae44b8d81f0387f9519101a6da442d543ab70ee0e482", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm"},
"credentials_obfuscation": {:hex, :credentials_obfuscation, "1.1.0", "513793cc20c18afc9e03e584b436192a751a8344890e03a8741c65c8d6866fab", [:rebar3], [], "hexpm"},
"db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm"},
"dialyxir": {:hex, :dialyxir, "1.0.0-rc.6", "78e97d9c0ff1b5521dd68041193891aebebce52fc3b93463c0a6806874557d7d", [:mix], [{:erlex, "~> 0.2.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm"},
"dns": {:hex, :dns, "2.1.2", "81c46d39f7934f0e73368355126e4266762cf227ba61d5889635d83b2d64a493", [:mix], [{:socket, "~> 0.3.13", [hex: :socket, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "2.2.9", "031d55df9bb430cb118e6f3026a87408d9ce9638737bda3871e5d727a3594aae", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm"},
"erlex": {:hex, :erlex, "0.2.2", "cb0e6878fdf86dc63509eaf2233a71fa73fc383c8362c8ff8e8b6f0c2bb7017c", [:mix], [], "hexpm"},
"esqlite": {:hex, :esqlite, "0.2.5", "cab6d87aeb5f33d848b9bb8a21129e9512ea608f930d4c63576942d8f7d72218", [:rebar3], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
"excoveralls": {:hex, :excoveralls, "0.10.6", "e2b9718c9d8e3ef90bc22278c3f76c850a9f9116faf4ebe9678063310742edc2", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"},
"farmbot_system_rpi": {:hex, :farmbot_system_rpi, "1.10.0-farmbot.1", "baf3e08e53adfaa4f6af76f544713b7943b00295cf41be5f117f4f33b5d68cc6", [:mix], [{:nerves, "~> 1.5.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.10.0", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_armv6_rpi_linux_gnueabi, "1.2.0", [hex: :nerves_toolchain_armv6_rpi_linux_gnueabi, repo: "hexpm", optional: false]}], "hexpm"},
"amqp": {:hex, :amqp, "1.4.0", "4172595d467b9360850a8eca254c5946af9970684d335d555a9f3410a0e43995", [:mix], [{:amqp_client, "~> 3.8.0", [hex: :amqp_client, repo: "hexpm", optional: false]}], "hexpm", "333ace582c4eacc65ebb8378358e0b8bd46474556f2855c417d4257bbf425dbf"},
"amqp_client": {:hex, :amqp_client, "3.8.2", "b50ac381c3c016a697d6ab8f08367043a08358cfeb8ee97832ccc7d101e59cef", [:make, :rebar3], [{:rabbit_common, "3.8.2", [hex: :rabbit_common, repo: "hexpm", optional: false]}], "hexpm", "f453386e02a7ae77e0aa88bf4cf071aa31cce54e1b8511ca805961b0d83f164e"},
"busybox": {:hex, :busybox, "0.1.4", "9b07860c0663e7d0ace3093100ede44860bd73c22b17c2941a4b17e25893cc36", [:make, :mix], [{:elixir_make, "~> 0.5", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "49f4a47821b31e1d5936aa7887109e4b6829a5c4d9233d5f3b2b62d6766e634d"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"},
"circuits_gpio": {:hex, :circuits_gpio, "0.4.3", "1a53dff1eaeefb9f67f4ebc2c1852b603683eedaa6053bed51c038dd64b978bb", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3b9e0879af04d3cb0db219954355e73b2b4ed1cd427014d613bea5c11654787b"},
"circuits_i2c": {:hex, :circuits_i2c, "0.3.5", "43e043d7efc3aead364061f8a7ed627f81ff7cef52bfa47cb629d8a68ca56a9f", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "37a301618b19fe82d311c9e2858e25a3c4f131fec44d9e0d0b18849e9434120e"},
"circuits_uart": {:hex, :circuits_uart, "1.4.0", "799abad2d5f355bd571c46de089e62c6341e6b08f9fdf51f4d53d50f5d5bbda9", [:mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f4543b823bd7ba2c7d4eb3ec634205003dfaaf141fbc4f668fe20eb193b18760"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
"cors_plug": {:hex, :cors_plug, "2.0.0", "238ddb479f92b38f6dc1ae44b8d81f0387f9519101a6da442d543ab70ee0e482", [:mix], [{:plug, "~> 1.3 or ~> 1.4 or ~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "118162367ef41448c9742ced8c8bc33ae2857d958d6b997e1db26402dd8c6f37"},
"cowboy": {:hex, :cowboy, "2.6.3", "99aa50e94e685557cad82e704457336a453d4abcb77839ad22dbe71f311fcc06", [:rebar3], [{:cowlib, "~> 2.7.3", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e5580029080f3f1ad17436fb97b0d5ed2ed4e4815a96bac36b5a992e20f58db6"},
"cowlib": {:hex, :cowlib, "2.7.3", "a7ffcd0917e6d50b4d5fb28e9e2085a0ceb3c97dea310505f7460ff5ed764ce9", [:rebar3], [], "hexpm", "1e1a3d176d52daebbecbbcdfd27c27726076567905c2a9d7398c54da9d225761"},
"credentials_obfuscation": {:hex, :credentials_obfuscation, "1.1.0", "513793cc20c18afc9e03e584b436192a751a8344890e03a8741c65c8d6866fab", [:rebar3], [], "hexpm", "2d1bc574d129ff76309a03874c245193c6375bc766734e008888e636b250d5df"},
"db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm", "5f0a16a58312a610d5eb0b07506280c65f5137868ad479045f2a2dc4ced80550"},
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
"dialyxir": {:hex, :dialyxir, "1.0.0-rc.6", "78e97d9c0ff1b5521dd68041193891aebebce52fc3b93463c0a6806874557d7d", [:mix], [{:erlex, "~> 0.2.1", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "49496d63267bc1a4614ffd5f67c45d9fc3ea62701a6797975bc98bc156d2763f"},
"dns": {:hex, :dns, "2.1.2", "81c46d39f7934f0e73368355126e4266762cf227ba61d5889635d83b2d64a493", [:mix], [{:socket, "~> 0.3.13", [hex: :socket, repo: "hexpm", optional: false]}], "hexpm", "6818589d8e59c03a2c73001e5cd7a957f99c30a796021aa32445ea14d0f3356b"},
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
"ecto": {:hex, :ecto, "2.2.9", "031d55df9bb430cb118e6f3026a87408d9ce9638737bda3871e5d727a3594aae", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm", "f1e20ddf41713b4db247443a3bea9045c4103b27c0e64b0c21ec50edde51fba8"},
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
"erlex": {:hex, :erlex, "0.2.2", "cb0e6878fdf86dc63509eaf2233a71fa73fc383c8362c8ff8e8b6f0c2bb7017c", [:mix], [], "hexpm", "423a8f6ac70b77f0001c18adbff2b10413afed6901c2975aa33151a9c1263307"},
"esqlite": {:hex, :esqlite, "0.2.5", "cab6d87aeb5f33d848b9bb8a21129e9512ea608f930d4c63576942d8f7d72218", [:rebar3], [], "hexpm", "3dd1163c8807b24a05ec4d88fd0f1bb286a2640ed340898fd792e3a67bb70f10"},
"ex_doc": {:hex, :ex_doc, "0.21.2", "caca5bc28ed7b3bdc0b662f8afe2bee1eedb5c3cf7b322feeeb7c6ebbde089d6", [:mix], [{:earmark, "~> 1.3.3 or ~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f1155337ae17ff7a1255217b4c1ceefcd1860b7ceb1a1874031e7a861b052e39"},
"excoveralls": {:hex, :excoveralls, "0.10.6", "e2b9718c9d8e3ef90bc22278c3f76c850a9f9116faf4ebe9678063310742edc2", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b06c73492aa9940c4c29cfc1356bcf5540ae318f17b423749a0615a66ee3e049"},
"farmbot_system_rpi": {:hex, :farmbot_system_rpi, "1.10.0-farmbot.2", "d26af0578dc3c7327399dfaf60c2ff113b2bc6d7a9b38bd478e18eeeb601f368", [:mix], [{:nerves, "~> 1.5.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.10.0", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_armv6_rpi_linux_gnueabi, "1.2.0", [hex: :nerves_toolchain_armv6_rpi_linux_gnueabi, repo: "hexpm", optional: false]}], "hexpm", "1c6c8b8b450e3b202eb5e6c2d771ef5bb7d9faa340bde6e1767e053ef05f1af8"},
"farmbot_system_rpi0": {:hex, :farmbot_system_rpi0, "1.8.0-farmbot.0", "017d5c50a462a30acee8de882c62c584131048b6efd7ea3367cbf56be50deebe", [:mix], [{:nerves, "~> 1.4", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.8.2", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_armv6_rpi_linux_gnueabi, "1.2.0", [hex: :nerves_toolchain_armv6_rpi_linux_gnueabi, repo: "hexpm", optional: false]}], "hexpm"},
"farmbot_system_rpi3": {:hex, :farmbot_system_rpi3, "1.10.0-farmbot.1", "8032728161829e80f526dd37d075864f5edf20738e3f0e24962d62664ac96937", [:mix], [{:nerves, "~> 1.5.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.10.0", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "1.2.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm"},
"fwup": {:hex, :fwup, "0.3.0", "2c360815565fcbc945ebbb34b58f156efacb7f8d64766f1cb3426919bb3f41ea", [:mix], [], "hexpm"},
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm"},
"gettext": {:hex, :gettext, "0.17.0", "abe21542c831887a2b16f4c94556db9c421ab301aee417b7c4fbde7fbdbe01ec", [:mix], [], "hexpm"},
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"},
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
"jsx": {:hex, :jsx, "2.9.0", "d2f6e5f069c00266cad52fb15d87c428579ea4d7d73a33669e12679e203329dd", [:mix, :rebar3], [], "hexpm"},
"lager": {:hex, :lager, "3.8.0", "3402b9a7e473680ca179fc2f1d827cab88dd37dd1e6113090c6f45ef05228a1c", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"},
"farmbot_system_rpi3": {:hex, :farmbot_system_rpi3, "1.10.0-farmbot.1", "8032728161829e80f526dd37d075864f5edf20738e3f0e24962d62664ac96937", [:mix], [{:nerves, "~> 1.5.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.10.0", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "1.2.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm", "40b35222d282619b4dbcd5ea64a6d1601ae6b0b06da31a1df5d0514bee947653"},
"fwup": {:hex, :fwup, "0.3.0", "2c360815565fcbc945ebbb34b58f156efacb7f8d64766f1cb3426919bb3f41ea", [:mix], [], "hexpm", "d12990ebda7d485d0eb7502df7aa9a56e66f67b5eda158c352db1de48e3f0518"},
"gen_state_machine": {:hex, :gen_state_machine, "2.0.5", "9ac15ec6e66acac994cc442dcc2c6f9796cf380ec4b08267223014be1c728a95", [:mix], [], "hexpm", "5cacd405e72b2609a7e1f891bddb80c53d0b3b7b0036d1648e7382ca108c41c8"},
"gettext": {:hex, :gettext, "0.17.0", "abe21542c831887a2b16f4c94556db9c421ab301aee417b7c4fbde7fbdbe01ec", [:mix], [], "hexpm", "e0b8598e802676c81e66b061a2148c37c03886b24a3ca86a1f98ed40693b94b3"},
"goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm", "99cb4128cffcb3227581e5d4d803d5413fa643f4eb96523f77d9e6937d994ceb"},
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
"jason": {:hex, :jason, "1.2.0", "10043418c42d2493d0ee212d3fddd25d7ffe484380afad769a0a38795938e448", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "116747dbe057794c3a3e4e143b7c8390b29f634e16c78a7f59ba75bfa6852e7f"},
"jsx": {:hex, :jsx, "2.9.0", "d2f6e5f069c00266cad52fb15d87c428579ea4d7d73a33669e12679e203329dd", [:mix, :rebar3], [], "hexpm", "8ee1db1cabafdd578a2776a6aaae87c2a8ce54b47b59e9ec7dab5d7eb71cd8dc"},
"lager": {:hex, :lager, "3.8.0", "3402b9a7e473680ca179fc2f1d827cab88dd37dd1e6113090c6f45ef05228a1c", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm", "f6cb541b688eab60730d8d286eb77256a5a9ad06eac10d43beaf55d07e68bbb6"},
"luer": {:git, "https://github.com/rvirding/luerl.git", "ce4e1b5a66a2a37efe2f8cd16e365ad9845b5015", []},
"luerl": {:git, "https://github.com/rvirding/luerl.git", "ce4e1b5a66a2a37efe2f8cd16e365ad9845b5015", []},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"},
"mdns_lite": {:hex, :mdns_lite, "0.6.1", "6be652b99612d6790594b7e24ad04efd064028be4b9f199a8924fd4a89dd8881", [:mix], [{:dns, "~> 2.1", [hex: :dns, repo: "hexpm", optional: false]}, {:vintage_net, "~> 0.6", [hex: :vintage_net, repo: "hexpm", optional: true]}], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"},
"makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "a10c6eb62cca416019663129699769f0c2ccf39428b3bb3c0cb38c718a0c186d"},
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
"mdns_lite": {:hex, :mdns_lite, "0.6.1", "6be652b99612d6790594b7e24ad04efd064028be4b9f199a8924fd4a89dd8881", [:mix], [{:dns, "~> 2.1", [hex: :dns, repo: "hexpm", optional: false]}, {:vintage_net, "~> 0.6", [hex: :vintage_net, repo: "hexpm", optional: true]}], "hexpm", "e664f7ddacf7b811f9b7cec0c06c4e39788971bbdce24a19dc0f83c137aef8a0"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"mimic": {:hex, :mimic, "1.1.3", "3bad83d5271b4faa7bbfef587417a6605cbbc802a353395d446a1e5f46fe7115", [:mix], [], "hexpm"},
"mox": {:hex, :mox, "0.5.1", "f86bb36026aac1e6f924a4b6d024b05e9adbed5c63e8daa069bd66fb3292165b", [:mix], [], "hexpm"},
"muontrap": {:hex, :muontrap, "0.5.1", "98fe96d0e616ee518860803a37a29eb23ffc2ca900047cb1bb7fd37521010093", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"nerves": {:hex, :nerves, "1.5.3", "14abb71fa1ce0cd281ffb6ba743c6c896b664efc3c2dd542f8682a55602176d8", [:mix], [{:distillery, "~> 2.1", [hex: :distillery, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.4.4", "12b0d9c84ec9f79c1b0ac0de1c575372ef972d0c58ce21c36bf354062c6222d9", [:mix], [{:nerves_runtime, "~> 0.6", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_hub": {:hex, :nerves_hub, "0.7.4", "0e104cad468c3d601ed423e116ea3422fbd31b7eedb263bcb2a5c489dca8b53b", [:mix], [{:fwup, "~> 0.3.0", [hex: :fwup, repo: "hexpm", optional: false]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nerves_hub_cli, "~> 0.9", [hex: :nerves_hub_cli, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.8", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:phoenix_client, "~> 0.7", [hex: :phoenix_client, repo: "hexpm", optional: false]}, {:websocket_client, "~> 1.3", [hex: :websocket_client, repo: "hexpm", optional: false]}, {:x509, "~> 0.5", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_hub_cli": {:hex, :nerves_hub_cli, "0.9.0", "ee02d6a4ce7706b7860df925a5a578c0856757123d7df56dfb38f85818f80aba", [:mix], [{:nerves_hub_user_api, "~> 0.6", [hex: :nerves_hub_user_api, repo: "hexpm", optional: false]}, {:pbcs, "~> 0.1", [hex: :pbcs, repo: "hexpm", optional: false]}, {:table_rex, "~> 2.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}, {:x509, "~> 0.3", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_hub_user_api": {:hex, :nerves_hub_user_api, "0.6.0", "14f7bd249275c647981e6601ebef909fd4036391aef010ff74d01d4799b90bdf", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.1 or ~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}, {:x509, "~> 0.3", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_runtime": {:hex, :nerves_runtime, "0.10.3", "8671c805262a6b8819a92b16afb100060af55a807a30f62395136c133e72b4ab", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.8.0", [hex: :system_registry, repo: "hexpm", optional: false]}, {:uboot_env, "~> 0.1.1", [hex: :uboot_env, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_system_br": {:hex, :nerves_system_br, "1.10.0", "29169ebad0415b916bf3b9890f821a91b70be9af93a7fd824aa661f17b193548", [:mix], [], "hexpm"},
"nerves_system_linter": {:hex, :nerves_system_linter, "0.3.0", "84e0f63c8ac196b16b77608bbe7df66dcf352845c4e4fb394bffd2b572025413", [:mix], [], "hexpm"},
"nerves_time": {:hex, :nerves_time, "0.3.2", "cbd1048701a756695cda6ec5835419e47505a7fe437f97088c9475dc6f8ab625", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:muontrap, "~> 0.5", [hex: :muontrap, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_toolchain_arm_unknown_linux_gnueabihf": {:hex, :nerves_toolchain_arm_unknown_linux_gnueabihf, "1.2.0", "ba48ce7c846ee12dfca8148dc7240988d96a3f2eb9c234bf08bffe4f0f7a3c62", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.6.0", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_toolchain_armv6_rpi_linux_gnueabi": {:hex, :nerves_toolchain_armv6_rpi_linux_gnueabi, "1.2.0", "007668c7ad1f73bad8fd54ad1a27a3b0fb91bca51b4af6bb3bbdac968ccae0ba", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.6.0", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm"},
"nerves_toolchain_ctng": {:hex, :nerves_toolchain_ctng, "1.6.0", "452f8589c1a58ac787477caab20a8cfc6671e345837ccc19beefe49ae35ba983", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}], "hexpm"},
"nimble_csv": {:hex, :nimble_csv, "0.6.0", "a3673f26d41f986774fe6060e309615343d3cb83a6d435754d8b1fdbd5764879", [:mix], [], "hexpm"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm"},
"one_dhcpd": {:hex, :one_dhcpd, "0.2.4", "2664f2e1ac72cbae0474a88d1a3d55019ccc3ee582ded382589511627a8ed516", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"pbcs": {:hex, :pbcs, "0.1.1", "199c7fd4af3351758378355909145a2d187c565555ed16bde30b5055114652ed", [:mix], [], "hexpm"},
"phoenix_client": {:hex, :phoenix_client, "0.10.0", "a77ace5495c400001808e96980673dd3b97b1048f296fd032991c52e8f5fe93d", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:websocket_client, "~> 1.3", [hex: :websocket_client, repo: "hexpm", optional: true]}], "hexpm"},
"phoenix_html": {:hex, :phoenix_html, "2.13.3", "850e292ff6e204257f5f9c4c54a8cb1f6fbc16ed53d360c2b780a3d0ba333867", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
"plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"},
"rabbit_common": {:hex, :rabbit_common, "3.8.2", "6f5653e7ba8bbf76447b126d1ac224e1be5ed853808542bd67cbcff87fbd2493", [:make, :rebar3], [{:credentials_obfuscation, "1.1.0", [hex: :credentials_obfuscation, repo: "hexpm", optional: false]}, {:jsx, "2.9.0", [hex: :jsx, repo: "hexpm", optional: false]}, {:lager, "3.8.0", [hex: :lager, repo: "hexpm", optional: false]}, {:ranch, "1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}, {:recon, "2.5.0", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
"recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm"},
"ring_logger": {:hex, :ring_logger, "0.8.0", "b1baddc269099b2afe2ea3a87b8e2b71e57331c0000038ae55090068aac679db", [:mix], [], "hexpm"},
"sbroker": {:hex, :sbroker, "1.0.0", "28ff1b5e58887c5098539f236307b36fe1d3edaa2acff9d6a3d17c2dcafebbd0", [:rebar3], [], "hexpm"},
"shoehorn": {:hex, :shoehorn, "0.6.0", "f9a1b7d6212cf18ba91c4f71c26076059df33cea4db2eb3c098bfa6673349412", [:mix], [{:distillery, "~> 2.1", [hex: :distillery, repo: "hexpm", optional: true]}], "hexpm"},
"socket": {:hex, :socket, "0.3.13", "98a2ab20ce17f95fb512c5cadddba32b57273e0d2dba2d2e5f976c5969d0c632", [:mix], [], "hexpm"},
"sqlite_ecto2": {:hex, :sqlite_ecto2, "2.3.1", "fe58926854c3962c4c8710bd1070dd4ba3717ba77250387794cb7a65f77006aa", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "2.2.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: false]}, {:sqlitex, "~> 1.4", [hex: :sqlitex, repo: "hexpm", optional: false]}], "hexpm"},
"sqlitex": {:hex, :sqlitex, "1.4.3", "a50f12d6aeb25f4ebb128453386c09bbba8f5abd3c7713dc5eaa92f359926ac5", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"},
"system_registry": {:hex, :system_registry, "0.8.2", "df791dc276652fcfb53be4dab823e05f8269b96ac57c26f86a67838dbc0eefe7", [:mix], [], "hexpm"},
"table_rex": {:hex, :table_rex, "2.0.0", "712783cbc2decb4d644d2ab8ad9315294f960c41b2cf0539308164922e352084", [:mix], [], "hexpm"},
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm"},
"tesla": {:hex, :tesla, "1.3.0", "f35d72f029e608f9cdc6f6d6fcc7c66cf6d6512a70cfef9206b21b8bd0203a30", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 0.4", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
"timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"toolshed": {:hex, :toolshed, "0.2.11", "0cd5312bd6a48f5b654b6ffa9239a63af9f3d200da414790fe25f066e14842a9", [:mix], [{:nerves_runtime, "~> 0.8", [hex: :nerves_runtime, repo: "hexpm", optional: true]}], "hexpm"},
"tzdata": {:hex, :tzdata, "0.5.21", "8cbf3607fcce69636c672d5be2bbb08687fe26639a62bdcc283d267277db7cf0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"uboot_env": {:hex, :uboot_env, "0.1.1", "b01e3ec0973e99473234f27839e29e63b5b81eba6a136a18a78d049d4813d6c5", [:mix], [], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm"},
"vintage_net": {:hex, :vintage_net, "0.7.5", "4e843a3f029a32cd5644aa3397bbb1463e123b06848e5a3c99cefc58af395a2a", [:make, :mix], [{:busybox, "~> 0.1.4", [hex: :busybox, repo: "hexpm", optional: true]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:gen_state_machine, "~> 2.0.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:muontrap, "~> 0.5.1", [hex: :muontrap, repo: "hexpm", optional: false]}], "hexpm"},
"vintage_net_direct": {:hex, :vintage_net_direct, "0.7.0", "6a8d95432fc2fb9a9e0225bf1a6dbb7c1ba097bb509a989e947ad3f44d2b0f28", [:mix], [{:one_dhcpd, "~> 0.2.3", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:vintage_net, "~> 0.7.0", [hex: :vintage_net, repo: "hexpm", optional: false]}], "hexpm"},
"vintage_net_ethernet": {:hex, :vintage_net_ethernet, "0.7.0", "b66a4da0846b4a55a471d15d8ab2bedbbef9c75c18f4e511c43777f7cfeffa09", [:mix], [{:vintage_net, "~> 0.7.0", [hex: :vintage_net, repo: "hexpm", optional: false]}], "hexpm"},
"vintage_net_wifi": {:hex, :vintage_net_wifi, "0.7.0", "be3d8275fa40ba357159033523606c74c8f612dbd60801840df60835453a6906", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:vintage_net, "~> 0.7.0", [hex: :vintage_net, repo: "hexpm", optional: false]}], "hexpm"},
"websocket_client": {:hex, :websocket_client, "1.3.0", "2275d7daaa1cdacebf2068891c9844b15f4fdc3de3ec2602420c2fb486db59b6", [:rebar3], [], "hexpm"},
"x509": {:hex, :x509, "0.8.0", "b286b9dbb32801f76f48bdea476304d280c64ce172ea330c23d8df7ea9e75ce6", [:mix], [], "hexpm"},
"muontrap": {:hex, :muontrap, "0.5.1", "98fe96d0e616ee518860803a37a29eb23ffc2ca900047cb1bb7fd37521010093", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3c11b7f151b202148912c73cbdd633b76fa68fabc26cc441c9d6d140e22290dc"},
"nerves": {:hex, :nerves, "1.5.4", "d5a2a29a642e92277d5680f40d0fadff17796e75faa82de87ba0bc920ffcf818", [:mix], [{:distillery, "~> 2.1", [hex: :distillery, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "283ce855f369ff209f3358d25e58f1ac941d58aef3ce7e8cc0be9919d25bf4f5"},
"nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.4.4", "12b0d9c84ec9f79c1b0ac0de1c575372ef972d0c58ce21c36bf354062c6222d9", [:mix], [{:nerves_runtime, "~> 0.6", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm", "98c40104d0d2c6e6e8cce22f8c8fd8ad5b4b97f8694e42a9101ca44befac38f0"},
"nerves_hub": {:hex, :nerves_hub, "0.7.4", "0e104cad468c3d601ed423e116ea3422fbd31b7eedb263bcb2a5c489dca8b53b", [:mix], [{:fwup, "~> 0.3.0", [hex: :fwup, repo: "hexpm", optional: false]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nerves_hub_cli, "~> 0.9", [hex: :nerves_hub_cli, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.8", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:phoenix_client, "~> 0.7", [hex: :phoenix_client, repo: "hexpm", optional: false]}, {:websocket_client, "~> 1.3", [hex: :websocket_client, repo: "hexpm", optional: false]}, {:x509, "~> 0.5", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm", "af1daf7e879f1175c9db1957340b1773f11a00e1c63eb591427d1bf7f3d40b47"},
"nerves_hub_cli": {:hex, :nerves_hub_cli, "0.9.0", "ee02d6a4ce7706b7860df925a5a578c0856757123d7df56dfb38f85818f80aba", [:mix], [{:nerves_hub_user_api, "~> 0.6", [hex: :nerves_hub_user_api, repo: "hexpm", optional: false]}, {:pbcs, "~> 0.1", [hex: :pbcs, repo: "hexpm", optional: false]}, {:table_rex, "~> 2.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}, {:x509, "~> 0.3", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm", "0d0c2cf36c8e784534d6ba916587cbc282b00d317b577e8b2972eccd5ffe6314"},
"nerves_hub_user_api": {:hex, :nerves_hub_user_api, "0.6.0", "14f7bd249275c647981e6601ebef909fd4036391aef010ff74d01d4799b90bdf", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.1 or ~> 1.3", [hex: :tesla, repo: "hexpm", optional: false]}, {:x509, "~> 0.3", [hex: :x509, repo: "hexpm", optional: false]}], "hexpm", "de682a5f5302d1f602a92f82fe2380abd658640ca25f620a8e9854e883020ea0"},
"nerves_runtime": {:hex, :nerves_runtime, "0.10.3", "8671c805262a6b8819a92b16afb100060af55a807a30f62395136c133e72b4ab", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.8.0", [hex: :system_registry, repo: "hexpm", optional: false]}, {:uboot_env, "~> 0.1.1", [hex: :uboot_env, repo: "hexpm", optional: false]}], "hexpm", "1ed7d228490f2138a028bf293f9f492b716da10d33f43615e4318731961f3866"},
"nerves_system_br": {:hex, :nerves_system_br, "1.10.0", "29169ebad0415b916bf3b9890f821a91b70be9af93a7fd824aa661f17b193548", [:mix], [], "hexpm", "3f13276c2e28141e6033d7c0c7b041f690727c3f72b8d0f5d9001acf6a18fe74"},
"nerves_system_linter": {:hex, :nerves_system_linter, "0.3.0", "84e0f63c8ac196b16b77608bbe7df66dcf352845c4e4fb394bffd2b572025413", [:mix], [], "hexpm", "bffbdfb116bc72cde6e408c34c0670b199846e9a8f0953cc1c9f1eea693821a1"},
"nerves_time": {:hex, :nerves_time, "0.3.2", "cbd1048701a756695cda6ec5835419e47505a7fe437f97088c9475dc6f8ab625", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:muontrap, "~> 0.5", [hex: :muontrap, repo: "hexpm", optional: false]}], "hexpm", "b2da78b6d98775c29a7bd296d4b293a89a849a0fed2a685774892b50b2b63444"},
"nerves_toolchain_arm_unknown_linux_gnueabihf": {:hex, :nerves_toolchain_arm_unknown_linux_gnueabihf, "1.2.0", "ba48ce7c846ee12dfca8148dc7240988d96a3f2eb9c234bf08bffe4f0f7a3c62", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.6.0", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm", "18df425fee48a9088bf941d3615c677b818b537310123c4b4c90b710e4a34180"},
"nerves_toolchain_armv6_rpi_linux_gnueabi": {:hex, :nerves_toolchain_armv6_rpi_linux_gnueabi, "1.2.0", "007668c7ad1f73bad8fd54ad1a27a3b0fb91bca51b4af6bb3bbdac968ccae0ba", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.6.0", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm", "4e843f405d4b8e6137419f94e5b8f491bff5a87b02ac2223e126182e8cec4256"},
"nerves_toolchain_ctng": {:hex, :nerves_toolchain_ctng, "1.6.0", "452f8589c1a58ac787477caab20a8cfc6671e345837ccc19beefe49ae35ba983", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}], "hexpm", "7ee5744dc606c6debf3e459ef122e77c13d6a1be9e093f7e29af3759896f9dbb"},
"nimble_csv": {:hex, :nimble_csv, "0.6.0", "a3673f26d41f986774fe6060e309615343d3cb83a6d435754d8b1fdbd5764879", [:mix], [], "hexpm", "b78e15f1c82fd963745ecc4073c47e1f0cdaf3622143342b8c30e9b5eaa546c6"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
"one_dhcpd": {:hex, :one_dhcpd, "0.2.4", "2664f2e1ac72cbae0474a88d1a3d55019ccc3ee582ded382589511627a8ed516", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "38f08c228d066153dbe352b150910345e395eacd2db3c19085d54c0baeeebacb"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"pbcs": {:hex, :pbcs, "0.1.1", "199c7fd4af3351758378355909145a2d187c565555ed16bde30b5055114652ed", [:mix], [], "hexpm", "7bf2553c32bc7948959e599de0b39745ef988b0914fdb2cf89ea2bed569c1357"},
"phoenix_client": {:hex, :phoenix_client, "0.10.0", "a77ace5495c400001808e96980673dd3b97b1048f296fd032991c52e8f5fe93d", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:websocket_client, "~> 1.3", [hex: :websocket_client, repo: "hexpm", optional: true]}], "hexpm", "9374a29a9f835125cec73a2b45086eedce8df6b4d7c5353fced11bb48a3d6800"},
"phoenix_html": {:hex, :phoenix_html, "2.13.3", "850e292ff6e204257f5f9c4c54a8cb1f6fbc16ed53d360c2b780a3d0ba333867", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8b01b3d6d39731ab18aa548d928b5796166d2500755f553725cfe967bafba7d9"},
"plug": {:hex, :plug, "1.8.3", "12d5f9796dc72e8ac9614e94bda5e51c4c028d0d428e9297650d09e15a684478", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "164baaeb382d19beee0ec484492aa82a9c8685770aee33b24ec727a0971b34d0"},
"plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "6cd8ddd1bd1fbfa54d3fc61d4719c2057dae67615395d58d40437a919a46f132"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm", "73c1682f0e414cfb5d9b95c8e8cd6ffcfdae699e3b05e1db744e58b7be857759"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"rabbit_common": {:hex, :rabbit_common, "3.8.2", "6f5653e7ba8bbf76447b126d1ac224e1be5ed853808542bd67cbcff87fbd2493", [:make, :rebar3], [{:credentials_obfuscation, "1.1.0", [hex: :credentials_obfuscation, repo: "hexpm", optional: false]}, {:jsx, "2.9.0", [hex: :jsx, repo: "hexpm", optional: false]}, {:lager, "3.8.0", [hex: :lager, repo: "hexpm", optional: false]}, {:ranch, "1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}, {:recon, "2.5.0", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm", "c36e9613093910ed3a86f43a21fddcd48c3015a81ba74586824bdc4b2ae5519b"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"recon": {:hex, :recon, "2.5.0", "2f7fcbec2c35034bade2f9717f77059dc54eb4e929a3049ca7ba6775c0bd66cd", [:mix, :rebar3], [], "hexpm", "72f3840fedd94f06315c523f6cecf5b4827233bed7ae3fe135b2a0ebeab5e196"},
"ring_logger": {:hex, :ring_logger, "0.8.0", "b1baddc269099b2afe2ea3a87b8e2b71e57331c0000038ae55090068aac679db", [:mix], [], "hexpm", "9b2f482e4346c13c11ef555f898202d0ddbfda6e2354e5c6e0559d2b4e0cf781"},
"sbroker": {:hex, :sbroker, "1.0.0", "28ff1b5e58887c5098539f236307b36fe1d3edaa2acff9d6a3d17c2dcafebbd0", [:rebar3], [], "hexpm", "ba952bfa35b374e1e5d84bc5f5efe8360c6f99dc93b3118f714a9a2dff6c9e19"},
"shoehorn": {:hex, :shoehorn, "0.6.0", "f9a1b7d6212cf18ba91c4f71c26076059df33cea4db2eb3c098bfa6673349412", [:mix], [{:distillery, "~> 2.1", [hex: :distillery, repo: "hexpm", optional: true]}], "hexpm", "e54a1f58a121caf8f0f3a355686b2661258b1bc0d4fffef8923bd7b11c2f9d79"},
"socket": {:hex, :socket, "0.3.13", "98a2ab20ce17f95fb512c5cadddba32b57273e0d2dba2d2e5f976c5969d0c632", [:mix], [], "hexpm", "f82ea9833ef49dde272e6568ab8aac657a636acb4cf44a7de8a935acb8957c2e"},
"sqlite_ecto2": {:hex, :sqlite_ecto2, "2.3.1", "fe58926854c3962c4c8710bd1070dd4ba3717ba77250387794cb7a65f77006aa", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "2.2.9", [hex: :ecto, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: false]}, {:sqlitex, "~> 1.4", [hex: :sqlitex, repo: "hexpm", optional: false]}], "hexpm", "a588e8e4ab9570c32a03605fabff86f0fee1040530d33edc4fc4392db4d81700"},
"sqlitex": {:hex, :sqlitex, "1.4.3", "a50f12d6aeb25f4ebb128453386c09bbba8f5abd3c7713dc5eaa92f359926ac5", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:esqlite, "~> 0.2.4", [hex: :esqlite, repo: "hexpm", optional: false]}], "hexpm", "0e1974b48684ba85255d2fe16c6106d52f5e759b260c95f676b23aa13b708a96"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"},
"system_registry": {:hex, :system_registry, "0.8.2", "df791dc276652fcfb53be4dab823e05f8269b96ac57c26f86a67838dbc0eefe7", [:mix], [], "hexpm", "f7acdede22c73ab0b3735eead7f2095efb2a7a6198366564205274db2ca2a8f8"},
"table_rex": {:hex, :table_rex, "2.0.0", "712783cbc2decb4d644d2ab8ad9315294f960c41b2cf0539308164922e352084", [:mix], [], "hexpm", "b183ff68abe9ace8764af4c2828767865d2c854d1d6f8cfd3660218166ed89a1"},
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
"tesla": {:hex, :tesla, "1.3.0", "f35d72f029e608f9cdc6f6d6fcc7c66cf6d6512a70cfef9206b21b8bd0203a30", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 0.4", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "93a7cacc5ca47997759cfa1d3ab25501d291e490908006d5be56f37f89d96693"},
"timex": {:hex, :timex, "3.5.0", "b0a23167da02d0fe4f1a4e104d1f929a00d348502b52432c05de875d0b9cffa5", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "b8fd8c9fcfaef1fa9c415e0792e2e82783c7ec8a282dfceef7d48158d4cfb3e1"},
"toolshed": {:hex, :toolshed, "0.2.11", "0cd5312bd6a48f5b654b6ffa9239a63af9f3d200da414790fe25f066e14842a9", [:mix], [{:nerves_runtime, "~> 0.8", [hex: :nerves_runtime, repo: "hexpm", optional: true]}], "hexpm", "f22ae95d77136f9f7db93cddd40d42bc8252d825f15a772a17d4c7947b6faad5"},
"tzdata": {:hex, :tzdata, "0.5.21", "8cbf3607fcce69636c672d5be2bbb08687fe26639a62bdcc283d267277db7cf0", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "69c9913feb9b9f84c7e70e448e1f425d450d95119800ba420c5121a59bdd12c6"},
"uboot_env": {:hex, :uboot_env, "0.1.1", "b01e3ec0973e99473234f27839e29e63b5b81eba6a136a18a78d049d4813d6c5", [:mix], [], "hexpm", "f7b82da0cb40c8db9c9fb1fc977780ab0c28d961ec1f3c7ab265c4352e4141ae"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},
"vintage_net": {:hex, :vintage_net, "0.7.5", "4e843a3f029a32cd5644aa3397bbb1463e123b06848e5a3c99cefc58af395a2a", [:make, :mix], [{:busybox, "~> 0.1.4", [hex: :busybox, repo: "hexpm", optional: true]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:gen_state_machine, "~> 2.0.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:muontrap, "~> 0.5.1", [hex: :muontrap, repo: "hexpm", optional: false]}], "hexpm", "cc2e3d3d1e5e04a15df32ce624d8c3cccc88432dcada48eccc6de9bec891acc2"},
"vintage_net_direct": {:hex, :vintage_net_direct, "0.7.0", "6a8d95432fc2fb9a9e0225bf1a6dbb7c1ba097bb509a989e947ad3f44d2b0f28", [:mix], [{:one_dhcpd, "~> 0.2.3", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:vintage_net, "~> 0.7.0", [hex: :vintage_net, repo: "hexpm", optional: false]}], "hexpm", "9c091687dccbc379ba3b8de01458a3e911321aa391e2c2ae3be7edadd0c51f76"},
"vintage_net_ethernet": {:hex, :vintage_net_ethernet, "0.7.0", "b66a4da0846b4a55a471d15d8ab2bedbbef9c75c18f4e511c43777f7cfeffa09", [:mix], [{:vintage_net, "~> 0.7.0", [hex: :vintage_net, repo: "hexpm", optional: false]}], "hexpm", "bff0fa24b153b80dab959a0697777c4de7c3272c387ffa618f73805d66757c3f"},
"vintage_net_wifi": {:hex, :vintage_net_wifi, "0.7.0", "be3d8275fa40ba357159033523606c74c8f612dbd60801840df60835453a6906", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:vintage_net, "~> 0.7.0", [hex: :vintage_net, repo: "hexpm", optional: false]}], "hexpm", "a9dfac64705f0b7cd7e0fc2338cf891eef091a49385805d0a42c22fb0aaa7c84"},
"websocket_client": {:hex, :websocket_client, "1.3.0", "2275d7daaa1cdacebf2068891c9844b15f4fdc3de3ec2602420c2fb486db59b6", [:rebar3], [], "hexpm", "b864fa076f059b615da4ab99240e515b26132ce4d2d0f9df5d7f22f01fa04b65"},
"x509": {:hex, :x509, "0.8.0", "b286b9dbb32801f76f48bdea476304d280c64ce172ea330c23d8df7ea9e75ce6", [:mix], [], "hexpm", "8aafaafdcafb1ea9f06bfc32c3b03ccc66f087e0faf36ef94c0195bb7a04157e"},
}

View File

@ -2,7 +2,7 @@ defmodule FarmbotOS.Platform.Target.NervesHubClient do
@moduledoc """
NervesHub.Client implementation.
This should be one of the very first processes to be started.
This should be one of the very first processes to be started.
Because it is started so early, it has to check for things that
might not be available in the environment. Environment is checked
in this order:
@ -397,8 +397,8 @@ defmodule FarmbotOS.Platform.Target.NervesHubClient do
%{is_applying_update: false, probably_connected: true} = state
) do
if should_auto_apply_update?() && update_available?() do
FarmbotCore.Logger.busy(1, "Applying OTA update")
spawn_link(fn -> NervesHub.update() end)
FarmbotCore.Logger.busy(1, "Applying OTA update (1)")
run_update_but_only_once()
{:noreply, %{state | is_applying_update: true}}
else
Process.send_after(self(), :checkup, @checkup_timeout_ms)
@ -423,7 +423,7 @@ defmodule FarmbotOS.Platform.Target.NervesHubClient do
{:handle_nerves_hub_error, error},
%{is_applying_update: true} = state
) do
FarmbotCore.Logger.error(1, "Error applying OTA: #{inspect(error)}")
FarmbotCore.Logger.error(1, "Error applying OTA (1): #{inspect(error)}")
{:noreply, state}
end
@ -449,7 +449,7 @@ defmodule FarmbotOS.Platform.Target.NervesHubClient do
def handle_cast({:handle_nerves_hub_fwup_message, {:error, _, reason}}, state) do
_ = set_ota_progress(100)
FarmbotCore.Logger.error(1, "Error applying OTA: #{inspect(reason)}")
FarmbotCore.Logger.error(1, "Error applying OTA (2): #{inspect(reason)}")
{:noreply, state}
end
@ -465,7 +465,7 @@ defmodule FarmbotOS.Platform.Target.NervesHubClient do
) do
case should_auto_apply_update?() do
true ->
FarmbotCore.Logger.busy(1, "Applying OTA update")
FarmbotCore.Logger.busy(1, "Applying OTA update (2)")
_ = set_update_available_in_bot_state()
_ = update_device_last_ota_checkup()
_ = set_firmware_needs_flash()
@ -474,7 +474,6 @@ defmodule FarmbotOS.Platform.Target.NervesHubClient do
_ ->
_ = set_update_available_in_bot_state()
_ = update_device_last_ota_checkup()
FarmbotCore.Logger.info(1, "New Farmbot OS is available!")
Process.send_after(self(), :checkup, @checkup_timeout_ms)
{:reply, :ignore, %{state | firmware_url: url}}
end
@ -484,7 +483,7 @@ defmodule FarmbotOS.Platform.Target.NervesHubClient do
_ = set_update_available_in_bot_state()
_ = update_device_last_ota_checkup()
_ = set_firmware_needs_flash()
FarmbotCore.Logger.busy(1, "Applying OTA update")
FarmbotCore.Logger.busy(1, "Applying OTA update (3)")
{:reply, :apply, %{state | is_applying_update: true}}
end
@ -498,9 +497,10 @@ defmodule FarmbotOS.Platform.Target.NervesHubClient do
_ = set_update_available_in_bot_state()
_ = update_device_last_ota_checkup()
_ = set_firmware_needs_flash()
FarmbotCore.Logger.busy(1, "Applying OTA update")
spawn_link(fn -> NervesHub.update() end)
FarmbotCore.Logger.busy(1, "Attempting OTA update...")
# This is where the NervesHub update gets called.
# Maybe we can check if the BotState has job progress for "FBOS_OTA"
run_update_but_only_once()
{:reply, data, %{state | is_applying_update: true}}
end
end
@ -511,38 +511,41 @@ defmodule FarmbotOS.Platform.Target.NervesHubClient do
ota_hour = Asset.device(:ota_hour)
timezone = Asset.device(:timezone)
# if ota_hour is nil, auto apply the update
if ota_hour && timezone do
# check that now.hour == device.ota_hour
case Timex.Timezone.convert(now, timezone) do
%{hour: ^ota_hour} ->
FarmbotCore.Logger.debug(
3,
"current hour: #{ota_hour} (utc=#{now.hour}) == ota_hour #{ota_hour}. auto_update=#{
auto_update
}"
)
result =
if ota_hour && timezone do
# check that now.hour == device.ota_hour
case Timex.Timezone.convert(now, timezone) do
%{hour: ^ota_hour} ->
FarmbotCore.Logger.debug(
3,
"current hour: #{ota_hour} (utc=#{now.hour}) == ota_hour #{
ota_hour
}. auto_update=#{auto_update}"
)
auto_update
auto_update
%{hour: now_hour} ->
FarmbotCore.Logger.debug(
3,
"current hour: #{now_hour} (utc=#{now.hour}) != ota_hour: #{
ota_hour
}. auto_update=#{auto_update}"
)
%{hour: now_hour} ->
FarmbotCore.Logger.debug(
3,
"current hour: #{now_hour} (utc=#{now.hour}) != ota_hour: #{
ota_hour
}. auto_update=#{auto_update}"
)
false
false
end
else
# ota_hour or timezone are nil
FarmbotCore.Logger.debug(
3,
"ota_hour = #{ota_hour || "null"} timezone = #{timezone || "null"}"
)
true
end
else
# ota_hour or timezone are nil
FarmbotCore.Logger.debug(
3,
"ota_hour = #{ota_hour || "null"} timezone = #{timezone || "null"}"
)
true
end
result && !currently_downloading?()
end
def update_available?() do
@ -615,7 +618,7 @@ defmodule FarmbotOS.Platform.Target.NervesHubClient do
Process.monitor(conn.pid)
{:ok, conn}
# Squash this log since it will be displayed for the
# Squash this log since it will be displayed for the
# main AMQP connection
{:error, :unknown_host} = err ->
err
@ -678,4 +681,18 @@ defmodule FarmbotOS.Platform.Target.NervesHubClient do
end
end
end
def currently_downloading?, do: BotState.job_in_progress?("FBOS_OTA")
def run_update_but_only_once do
if currently_downloading?() do
FarmbotCore.Logger.error(
1,
"Can't perform OTA. OTA alread in progress. Restart device if problem persists."
)
else
FarmbotCore.Logger.success(1, "OTA started.")
spawn_link(fn -> NervesHub.update() end)
end
end
end

View File

@ -188,7 +188,7 @@ defmodule FarmbotOS.Platform.Target.Network do
_meta},
state
) do
FarmbotCore.Logger.warn(
FarmbotCore.Logger.success(
1,
"Interface #{ifname} connected to local area network"
)
@ -202,7 +202,7 @@ defmodule FarmbotOS.Platform.Target.Network do
_meta},
state
) do
FarmbotCore.Logger.warn(1, "Interface #{ifname} connected to internet")
FarmbotCore.Logger.success(1, "Interface #{ifname} connected to internet")
state = cancel_network_not_found_timer(state)
FarmbotTelemetry.event(:network, :wan_connect, nil, interface: ifname)
{:noreply, %{state | first_connect?: false}}

View File

@ -9,7 +9,7 @@ defmodule FarmbotOS.SysCalls.FarmwareTest do
expect(FarmbotCore.LogExecutor, :execute, fn log ->
expected =
"Farmware did not exit after 30.0 seconds. Terminating :FAKE_PID"
"Farmware did not exit after 60.0 seconds. Terminating :FAKE_PID"
assert log.message == expected
:ok