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

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

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