commit
a9391dfb05
|
@ -25,7 +25,6 @@ fetch_and_compile_deps: &fetch_and_compile_deps
|
|||
name: Fetch and compile Elixir dependencies
|
||||
command: |
|
||||
mix deps.get
|
||||
mix deps.compile
|
||||
mix compile
|
||||
|
||||
install_arduino: &install_arduino
|
||||
|
@ -48,7 +47,16 @@ jobs:
|
|||
- v3-arduino-cache-{{ checksum ".circleci/setup-arduino.sh" }}
|
||||
- <<: *install_arduino
|
||||
- <<: *install_hex_archives
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v3-dep-cache-{{ checksum "mix.lock.host" }}
|
||||
- <<: *fetch_and_compile_deps
|
||||
- save_cache:
|
||||
key: v3-dep-cache-{{ checksum "mix.lock.host" }}
|
||||
paths:
|
||||
- _build/host
|
||||
- _build/arduino
|
||||
- deps/host
|
||||
- save_cache:
|
||||
key: v3-arduino-cache-{{ checksum ".circleci/setup-arduino.sh" }}
|
||||
paths:
|
||||
|
@ -72,15 +80,27 @@ jobs:
|
|||
- <<: *install_arduino
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v3-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
- v4-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
- <<: *install_hex_archives
|
||||
- <<: *fetch_and_compile_deps
|
||||
- run: mix firmware
|
||||
- save_cache:
|
||||
key: v3-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
key: v4-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
paths:
|
||||
- _build/rpi3
|
||||
- _build/host
|
||||
- _build/arduino
|
||||
- deps/rpi3
|
||||
- deps/host
|
||||
- ~/.nerves
|
||||
- run: mix firmware.slack --channels C58DCU4A3; echo "Hack."
|
||||
- run:
|
||||
name: Send firmware asset to slack.
|
||||
command: |
|
||||
if [ $(mix firmware.slack --channels C58DCU4A3) -eq 0 ]; then
|
||||
echo "Firmware upload success."
|
||||
else
|
||||
echo "Firmware upload fail."
|
||||
fi
|
||||
- run: mkdir -p artifacts
|
||||
- run:
|
||||
name: Decode fwup priv key
|
||||
|
@ -110,14 +130,18 @@ jobs:
|
|||
- <<: *install_hex_archives
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v3-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
- v4-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
- run: mix deps.get
|
||||
- run: mix deps.compile
|
||||
- run: mix compile
|
||||
- run: mix firmware
|
||||
- save_cache:
|
||||
key: v3-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
key: v4-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
paths:
|
||||
- _build/rpi3
|
||||
- _build/host
|
||||
- _build/arduino
|
||||
- deps/rpi3
|
||||
- deps/host
|
||||
- ~/.nerves
|
||||
- run: mkdir -p artifacts
|
||||
- run:
|
||||
|
@ -178,14 +202,18 @@ jobs:
|
|||
- <<: *install_hex_archives
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v3-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
- v4-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
- run: mix deps.get
|
||||
- run: mix deps.compile
|
||||
- run: mix compile
|
||||
- run: mix firmware
|
||||
- save_cache:
|
||||
key: v3-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
key: v4-dependency-cache-{{ checksum "mix.lock.rpi3" }}
|
||||
paths:
|
||||
- _build/rpi3
|
||||
- _build/host
|
||||
- _build/arduino
|
||||
- deps/rpi3
|
||||
- deps/host
|
||||
- ~/.nerves
|
||||
- run: mkdir -p artifacts
|
||||
- run:
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
# 6.2.0
|
||||
* Farmbot Settings are now synced with Farmbot API.
|
||||
* Refactor Syncing to not make unnecessary HTTP requests.
|
||||
* Estop status is now much faster.
|
||||
* Add dns checkup for users with factory resetting disabled to make tokens refresh faster.
|
||||
* Opting into beta updates will refresh farmbot's token.
|
||||
|
||||
# 6.1.2
|
||||
* Fix fw hardware being reset on os upgrade.
|
||||
* Bump arduino-firmware version to 6.0.1
|
||||
|
|
8
Makefile
8
Makefile
|
@ -1,11 +1,5 @@
|
|||
# Erlang Nif Stuff
|
||||
ifeq ($(ERL_EI_INCLUDE_DIR),)
|
||||
ERL_ROOT_DIR = $(shell erl -eval "io:format(\"~s~n\", [code:root_dir()])" -s init stop -noshell)
|
||||
ifeq ($(ERL_ROOT_DIR),)
|
||||
$(error Could not find the Erlang installation. Check to see that 'erl' is in your PATH)
|
||||
endif
|
||||
ERL_EI_INCLUDE_DIR = "$(ERL_ROOT_DIR)/usr/include"
|
||||
ERL_EI_LIBDIR = "$(ERL_ROOT_DIR)/usr/lib"
|
||||
$(error ERL_EI_INCLUDE_DIR not set. Invoke via mix)
|
||||
endif
|
||||
|
||||
# Set Erlang-specific compile and linker flags
|
||||
|
|
|
@ -6,6 +6,8 @@ config :logger,
|
|||
|
||||
config :farmbot, data_path: "/root"
|
||||
|
||||
config :farmbot, profile: System.get_env("FBOS_PROFILE")
|
||||
|
||||
# Disable tzdata autoupdates because it tries to dl the update file
|
||||
# Before we have network or ntp.
|
||||
config :tzdata, :autoupdate, :disabled
|
||||
|
@ -81,3 +83,6 @@ config :nerves_firmware_ssh, authorized_keys: local_key
|
|||
config :shoehorn,
|
||||
init: [:nerves_runtime, :nerves_init_gadget],
|
||||
app: :farmbot
|
||||
|
||||
config :nerves, :firmware,
|
||||
rootfs_overlay: "overlay/"
|
||||
|
|
16
docs/PROFILES.md
Normal file
16
docs/PROFILES.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Development Profiles
|
||||
Profiles are loaded synchronously right after the local settings database is setup.
|
||||
A profile can modify any global setting to farmbot os. Profiles are *NOT* loaded
|
||||
at compile time. This means, that if deployed to a target such as Raspberry Pi 3,
|
||||
your host environment variables will not be loaded.
|
||||
|
||||
# Selecting a profile
|
||||
Load a profile by doing:
|
||||
```bash
|
||||
FBOS_PROFILE=profile_name iex -S mix
|
||||
```
|
||||
|
||||
## Chaining profiles
|
||||
Profiles can be chained with commas. They will be evaluated in order supplied.
|
||||
```bash
|
||||
FBOS_PROFILE=admin,setup_arduino iex -S mix
|
|
@ -20,6 +20,7 @@ defmodule Farmbot.Bootstrap.AuthTask do
|
|||
|
||||
def init([]) do
|
||||
timer = Process.send_after(self(), :refresh, @refresh_time)
|
||||
Farmbot.System.Registry.subscribe(self())
|
||||
{:ok, timer, :hibernate}
|
||||
end
|
||||
|
||||
|
@ -29,7 +30,7 @@ defmodule Farmbot.Bootstrap.AuthTask do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_info(:refresh, _old_timer) do
|
||||
defp do_refresh do
|
||||
auth_task = Application.get_env(:farmbot, :behaviour)[:authorization]
|
||||
{email, pass, server} = {fetch_email(), fetch_pass(), fetch_server()}
|
||||
# Logger.busy(3, "refreshing token: #{email} - #{server}")
|
||||
|
@ -43,15 +44,27 @@ defmodule Farmbot.Bootstrap.AuthTask do
|
|||
if get_config_value(:bool, "settings", "auto_sync") do
|
||||
Farmbot.Repo.flip()
|
||||
end
|
||||
Farmbot.System.Registry.dispatch :authorization, :new_token
|
||||
restart_transports()
|
||||
refresh_timer(self())
|
||||
{:error, err} ->
|
||||
msg = "Token failed to reauthorize: #{email} - #{server} #{inspect err}"
|
||||
Logger.error(1, msg)
|
||||
refresh_timer(self(), 30_000)
|
||||
# If refresh failed, try again more often
|
||||
refresh_timer(self(), 15_000)
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(:refresh, _old_timer) do
|
||||
do_refresh()
|
||||
end
|
||||
|
||||
def handle_info({Farmbot.System.Registry, {:network, :dns_up}}, _old_timer) do
|
||||
do_refresh()
|
||||
end
|
||||
|
||||
def handle_info({Farmbot.System.Registry, _}, timer), do: {:noreply, timer}
|
||||
|
||||
def handle_call(:force_refresh, _, old_timer) do
|
||||
Logger.info 1, "Forcing a token refresh."
|
||||
if Process.read_timer(old_timer) do
|
||||
|
|
153
lib/farmbot/bootstrap/settings_sync.ex
Normal file
153
lib/farmbot/bootstrap/settings_sync.ex
Normal file
|
@ -0,0 +1,153 @@
|
|||
defmodule Farmbot.Bootstrap.SettingsSync do
|
||||
@moduledoc "Handles uploading and downloading of FBOS configs."
|
||||
use Task, restart: :transient
|
||||
use Farmbot.Logger
|
||||
import Farmbot.System.ConfigStorage, only: [get_config_value: 3, update_config_value: 4, get_config_as_map: 0]
|
||||
|
||||
def start_link() do
|
||||
run()
|
||||
:ignore
|
||||
end
|
||||
|
||||
def run() do
|
||||
with {:ok, %{body: body, status_code: 200}} <- Farmbot.HTTP.get("/api/fbos_config"),
|
||||
{:ok, data} <- Poison.decode(body)
|
||||
do
|
||||
do_sync_settings(data)
|
||||
update_config_value(:bool, "settings", "ignore_fbos_config", false)
|
||||
:ok
|
||||
else
|
||||
{:ok, status_code: code} ->
|
||||
Logger.error 1, "HTTP error syncing settings: #{code}"
|
||||
:ok
|
||||
err ->
|
||||
Logger.error 1, "Error syncing settings: #{inspect err}"
|
||||
:ok
|
||||
end
|
||||
rescue
|
||||
err ->
|
||||
update_config_value(:bool, "settings", "ignore_fbos_config", false)
|
||||
Logger.error 1, "Error syncing settings: #{Exception.message(err)} #{inspect System.stacktrace()}"
|
||||
end
|
||||
|
||||
def apply_map(old_map, new_map) do
|
||||
old_map = take_valid(old_map)
|
||||
new_map = take_valid(new_map)
|
||||
Map.new(new_map, fn({key, new_value}) ->
|
||||
# Logger.debug 1, "Applying #{key} #{inspect old_map[key]} over #{inspect new_value}"
|
||||
if old_map[key] != new_value do
|
||||
apply_to_config_storage key, new_value
|
||||
end
|
||||
{key, new_value}
|
||||
end)
|
||||
end
|
||||
|
||||
# TODO: This should be moved to ConfigStorage module maybe?
|
||||
@bool_keys [
|
||||
"auto_sync",
|
||||
"beta_opt_in",
|
||||
"disable_factory_reset",
|
||||
"firmware_output_log",
|
||||
"firmware_input_log",
|
||||
"sequence_body_log",
|
||||
"sequence_complete_log",
|
||||
"sequence_init_log",
|
||||
"arduino_debug_messages",
|
||||
"os_auto_update"
|
||||
]
|
||||
|
||||
@string_keys [
|
||||
"firmware_hardware"
|
||||
]
|
||||
|
||||
@float_keys [
|
||||
"network_not_found_timer"
|
||||
]
|
||||
|
||||
defp apply_to_config_storage(key, val)
|
||||
when key in @bool_keys and (is_nil(val) or is_boolean(val)) do
|
||||
Logger.success 2, "Updating: #{key} => #{inspect val}"
|
||||
update_config_value(:bool, "settings", key, val)
|
||||
end
|
||||
|
||||
defp apply_to_config_storage(key, val)
|
||||
when key in @string_keys and (is_nil(val) or is_binary(val)) do
|
||||
Logger.success 2, "Updating: #{key} => #{inspect val}"
|
||||
update_config_value(:string, "settings", key, val)
|
||||
end
|
||||
|
||||
defp apply_to_config_storage(key, val)
|
||||
when key in @float_keys and (is_nil(val) or is_number(val)) do
|
||||
Logger.success 2, "Updating: #{key} => #{inspect val}"
|
||||
if val do
|
||||
update_config_value(:float, "settings", key, val / 1)
|
||||
else
|
||||
update_config_value(:float, "settings", key, val)
|
||||
end
|
||||
end
|
||||
|
||||
defp apply_to_config_storage(key, val) do
|
||||
Logger.error 1, "Unknown pair: #{key} => #{inspect val}"
|
||||
end
|
||||
|
||||
def do_sync_settings(%{"api_migrated" => true} = api_data) do
|
||||
Logger.info 3, "API is the source of truth; Downloading data."
|
||||
old_config = get_config_as_map()["settings"]
|
||||
apply_map(old_config, api_data)
|
||||
:ok
|
||||
end
|
||||
|
||||
def do_sync_settings(_unimportant_data) do
|
||||
Logger.info 3, "FBOS is the source of truth; Uploading data."
|
||||
update_config_value(:bool, "settings", "ignore_fbos_config", true)
|
||||
auto_sync = get_config_value(:bool, "settings", "auto_sync")
|
||||
beta_opt_in = get_config_value(:bool, "settings", "beta_opt_in")
|
||||
disable_factory_reset = get_config_value(:bool, "settings", "disable_factory_reset")
|
||||
firmware_output_log = get_config_value(:bool, "settings", "firmware_output_log")
|
||||
firmware_input_log = get_config_value(:bool, "settings", "firmware_input_log")
|
||||
sequence_body_log = get_config_value(:bool, "settings", "sequence_body_log")
|
||||
sequence_complete_log = get_config_value(:bool, "settings", "sequence_complete_log")
|
||||
sequence_init_log = get_config_value(:bool, "settings", "sequence_init_log")
|
||||
arduino_debug_messages = get_config_value(:bool, "settings", "arduino_debug_messages")
|
||||
os_auto_update = get_config_value(:bool, "settings", "os_auto_update")
|
||||
firmware_hardware = get_config_value(:string, "settings", "firmware_hardware")
|
||||
network_not_found_timer = get_config_value(:float, "settings", "network_not_found_timer")
|
||||
payload = %{
|
||||
api_migrated: true,
|
||||
auto_sync: auto_sync,
|
||||
beta_opt_in: beta_opt_in,
|
||||
disable_factory_reset: disable_factory_reset,
|
||||
firmware_output_log: firmware_output_log,
|
||||
firmware_input_log: firmware_input_log,
|
||||
sequence_body_log: sequence_body_log,
|
||||
sequence_complete_log: sequence_complete_log,
|
||||
sequence_init_log: sequence_init_log,
|
||||
arduino_debug_messages: arduino_debug_messages,
|
||||
os_auto_update: os_auto_update,
|
||||
firmware_hardware: firmware_hardware,
|
||||
network_not_found_timer: network_not_found_timer,
|
||||
} |> Poison.encode!()
|
||||
Farmbot.HTTP.delete!("/api/fbos_config")
|
||||
Farmbot.HTTP.put!("/api/fbos_config", payload)
|
||||
update_config_value(:bool, "settings", "ignore_fbos_config", false)
|
||||
:ok
|
||||
end
|
||||
|
||||
@keys [
|
||||
"auto_sync",
|
||||
"beta_opt_in",
|
||||
"disable_factory_reset",
|
||||
"firmware_output_log",
|
||||
"firmware_input_log",
|
||||
"sequence_body_log",
|
||||
"sequence_complete_log",
|
||||
"sequence_init_log",
|
||||
"arduino_debug_messages",
|
||||
"os_auto_update",
|
||||
"firmware_hardware",
|
||||
"network_not_found_timer"
|
||||
]
|
||||
def take_valid(map) do
|
||||
Map.take(map, @keys ++ Enum.map(@keys, &String.to_atom(&1)))
|
||||
end
|
||||
end
|
|
@ -125,11 +125,12 @@ defmodule Farmbot.Bootstrap.Supervisor do
|
|||
|
||||
children = [
|
||||
worker(Farmbot.Bootstrap.AuthTask, []),
|
||||
supervisor(Farmbot.HTTP.Supervisor, []),
|
||||
worker(Farmbot.Bootstrap.SettingsSync, [], [restart: :transient]),
|
||||
supervisor(Farmbot.Firmware.Supervisor, []),
|
||||
supervisor(Farmbot.BotState.Supervisor, []),
|
||||
supervisor(Farmbot.BotState.Transport.Supervisor, []),
|
||||
supervisor(Farmbot.FarmEvent.Supervisor, []),
|
||||
supervisor(Farmbot.HTTP.Supervisor, []),
|
||||
supervisor(Farmbot.Repo.Supervisor, []),
|
||||
supervisor(Farmbot.Farmware.Supervisor, []),
|
||||
supervisor(Farmbot.Regimen.Supervisor, []),
|
||||
|
|
|
@ -276,6 +276,7 @@ defmodule Farmbot.BotState do
|
|||
end
|
||||
|
||||
defp do_handle([{:config, "settings", key, val} | rest], state) do
|
||||
# Logger.debug 1, "Got config update: #{inspect key} => #{inspect val}"
|
||||
new_config = Map.put(state.configuration, key, val)
|
||||
new_state = %{state | configuration: new_config}
|
||||
do_handle(rest, new_state)
|
||||
|
|
|
@ -178,6 +178,8 @@ defmodule Farmbot.BotState.Transport.AMQP do
|
|||
["bot", ^device, "sync", resource, _]
|
||||
when resource in ["Log", "User", "Image", "WebcamFeed"] ->
|
||||
{:noreply, [], state}
|
||||
["bot", ^device, "sync", "FbosConfig", id] ->
|
||||
handle_fbos_config(id, payload, state)
|
||||
["bot", ^device, "sync", resource, id] ->
|
||||
handle_sync_cmd(resource, id, payload, state)
|
||||
["bot", ^device, "logs"] -> {:noreply, [], state}
|
||||
|
@ -231,6 +233,28 @@ defmodule Farmbot.BotState.Transport.AMQP do
|
|||
{:noreply, [], state}
|
||||
end
|
||||
|
||||
def handle_fbos_config(_, _, %{state_cache: nil} = state) do
|
||||
# Don't update fbos config, if we don't have a state cache for whatever reason.
|
||||
{:noreply, [], state}
|
||||
end
|
||||
|
||||
def handle_fbos_config(_id, payload, state) do
|
||||
if get_config_value(:bool, "settings", "ignore_fbos_config") do
|
||||
{:noreply, [], state}
|
||||
else
|
||||
case Poison.decode(payload) do
|
||||
# TODO(Connor) What do I do with deletes?
|
||||
{:ok, %{"body" => nil}} -> {:noreply, [], state}
|
||||
{:ok, %{"body" => config}} ->
|
||||
# Logger.info 1, "Got fbos config from amqp: #{inspect config}"
|
||||
old = state.state_cache.configuration
|
||||
updated = Farmbot.Bootstrap.SettingsSync.apply_map(old, config)
|
||||
push_bot_state(state.chan, state.bot, %{state.state_cache | configuration: updated})
|
||||
{:noreply, [], state}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp push_bot_log(chan, bot, log) do
|
||||
json = Poison.encode!(log)
|
||||
:ok = AMQP.Basic.publish chan, @exchange, "bot.#{bot}.logs", json
|
||||
|
|
|
@ -4,9 +4,10 @@ defmodule Farmbot.CeleryScript.AST.Node.ConfigUpdate do
|
|||
use Farmbot.Logger
|
||||
allow_args [:package]
|
||||
|
||||
def execute(%{package: :farmbot_os}, body, env) do
|
||||
def execute(%{package: :farmbot_os}, _body, env) do
|
||||
Logger.warn 2, "`config_update` for FBOS is depricated."
|
||||
env = mutate_env(env)
|
||||
do_reduce_os(body, env)
|
||||
{:ok, env}
|
||||
end
|
||||
|
||||
def execute(%{package: :arduino_firmware}, body, env) do
|
||||
|
@ -14,21 +15,6 @@ defmodule Farmbot.CeleryScript.AST.Node.ConfigUpdate do
|
|||
do_reduce_fw(body, env)
|
||||
end
|
||||
|
||||
defp do_reduce_os([%{args: %{label: key, value: value}} | rest], env) do
|
||||
case lookup_os_config(key, value) do
|
||||
{:ok, {type, group, value}} ->
|
||||
Farmbot.System.ConfigStorage.update_config_value(type, group, key, value)
|
||||
Logger.success 3, "Updating: #{inspect key}: #{value}"
|
||||
do_reduce_os(rest, env)
|
||||
{:error, reason} ->
|
||||
{:error, reason, env}
|
||||
end
|
||||
end
|
||||
|
||||
defp do_reduce_os([], env) do
|
||||
{:ok, env}
|
||||
end
|
||||
|
||||
defp do_reduce_fw([%{args: %{label: key, value: value}} | rest], env) do
|
||||
case Farmbot.Firmware.update_param(:"#{key}", value) do
|
||||
:ok -> do_reduce_fw(rest, env)
|
||||
|
@ -37,41 +23,4 @@ defmodule Farmbot.CeleryScript.AST.Node.ConfigUpdate do
|
|||
end
|
||||
|
||||
defp do_reduce_fw([], env), do: {:ok, env}
|
||||
|
||||
defp lookup_os_config("os_auto_update", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}}
|
||||
defp lookup_os_config("auto_sync", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}}
|
||||
defp lookup_os_config("timezone", val), do: {:ok, {:string, "settings", val}}
|
||||
|
||||
defp lookup_os_config("disable_factory_reset", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}}
|
||||
|
||||
defp lookup_os_config("sequence_init_log", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}}
|
||||
defp lookup_os_config("sequence_body_log", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}}
|
||||
defp lookup_os_config("sequence_complete_log", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}}
|
||||
defp lookup_os_config("arduino_debug_messages", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}}
|
||||
defp lookup_os_config("firmware_input_log", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}}
|
||||
defp lookup_os_config("firmware_output_log", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}}
|
||||
defp lookup_os_config("beta_opt_in", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}}
|
||||
defp lookup_os_config("email_on_estop", val), do: {:ok, {:bool, "settings", format_bool_for_os(val)}}
|
||||
|
||||
defp lookup_os_config("network_not_found_timer", val) when val > 0, do: {:ok, {:float, "settings", to_float(val)}}
|
||||
defp lookup_os_config("network_not_found_timer", _val), do: {:error, "network_not_found_timer must be greater than zero"}
|
||||
|
||||
defp lookup_os_config("firmware_hardware", "farmduino"), do: {:ok, {:string, "settings", "farmduino"}}
|
||||
defp lookup_os_config("firmware_hardware", "arduino"), do: {:ok, {:string, "settings", "arduino"}}
|
||||
defp lookup_os_config("firmware_hardware", unknown), do: {:error, "unknown hardware: #{unknown}"}
|
||||
|
||||
defp lookup_os_config(unknown_config, _), do: {:error, "unknown config: #{unknown_config}"}
|
||||
|
||||
defp format_bool_for_os(1), do: true
|
||||
defp format_bool_for_os(0), do: false
|
||||
defp format_bool_for_os(true), do: true
|
||||
defp format_bool_for_os(false), do: false
|
||||
|
||||
defp to_float(int) when is_integer(int) do
|
||||
int / 1
|
||||
end
|
||||
|
||||
defp to_float(float) when is_float(float) do
|
||||
float
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Farmbot.CeleryScript.AST.Node.FactoryReset do
|
|||
Farmbot.BotState.set_sync_status(:maintenance)
|
||||
Farmbot.BotState.force_state_push()
|
||||
Farmbot.System.ConfigStorage.update_config_value(:bool, "settings", "disable_factory_reset", false)
|
||||
Farmbot.HTTP.delete("/api/fbos_config")
|
||||
Logger.warn 1, "Farmbot OS going down for factory reset!"
|
||||
Farmbot.System.factory_reset "CeleryScript request."
|
||||
{:ok, env}
|
||||
|
|
|
@ -16,7 +16,7 @@ defmodule Farmbot.CeleryScript.AST.Node.InstallFirstPartyFarmware do
|
|||
|
||||
defp do_sync_repo(env) do
|
||||
case Farmbot.Farmware.Installer.sync_repo(@fpf_url) do
|
||||
:ok -> {:ok, env}
|
||||
{:ok, _} -> {:ok, env}
|
||||
{:error, reason} -> {:error, reason, env}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -217,9 +217,14 @@ defmodule Farmbot.Firmware do
|
|||
# Logger.busy 3, "FW Starting: #{fun}: #{inspect from}"
|
||||
case apply(state.handler_mod, fun, [state.handler | args]) do
|
||||
:ok ->
|
||||
if fun == :emergency_unlock, do: Farmbot.System.GPIO.Leds.led_status_ok()
|
||||
timer = Process.send_after(self(), :timeout, state.timeout_ms)
|
||||
{:noreply, dispatch, %{state | current: current, timer: timer}}
|
||||
if fun == :emergency_unlock do
|
||||
Farmbot.System.GPIO.Leds.led_status_ok()
|
||||
new_dispatch = [{:informational_settings, %{busy: false, locked: false}} | dispatch]
|
||||
{:noreply, new_dispatch, %{state | current: current, timer: timer}}
|
||||
else
|
||||
{:noreply, dispatch, %{state | current: current, timer: timer}}
|
||||
end
|
||||
{:error, _} = res ->
|
||||
do_reply(%{state | current: current}, res)
|
||||
{:noreply, dispatch, %{state | current: nil}}
|
||||
|
|
|
@ -41,7 +41,7 @@ defmodule Farmbot.Firmware.UartHandler.AutoDetector do
|
|||
:ignore
|
||||
end
|
||||
|
||||
defp update_fw_handler(fw_handler) do
|
||||
def update_fw_handler(fw_handler) do
|
||||
old = Application.get_all_env(:farmbot)[:behaviour]
|
||||
new = Keyword.put(old, :firmware_handler, fw_handler)
|
||||
Application.put_env(:farmbot, :behaviour, new)
|
||||
|
|
|
@ -88,6 +88,24 @@ defmodule Farmbot.HTTP do
|
|||
request(:put, url, body, headers, opts)
|
||||
end
|
||||
|
||||
def put!(url, body, headers \\ [], opts \\ [])
|
||||
|
||||
def put!(url, body, headers, opts) do
|
||||
request!(:put, url, body, headers, opts)
|
||||
end
|
||||
|
||||
def delete(url, headers \\ [], opts \\ [])
|
||||
|
||||
def delete(url, headers, opts) do
|
||||
request!(:delete, url, "", headers, opts)
|
||||
end
|
||||
|
||||
def delete!(url, headers \\ [], opts \\ [])
|
||||
|
||||
def delete!(url, headers, opts) do
|
||||
request!(:delete, url, "", headers, opts)
|
||||
end
|
||||
|
||||
@doc "Download a file to the filesystem."
|
||||
def download_file(url,
|
||||
path,
|
||||
|
|
|
@ -298,7 +298,7 @@ defmodule Farmbot.Repo do
|
|||
mod = Module.concat(["Farmbot", "Repo", kind])
|
||||
# an object was deleted.
|
||||
if Code.ensure_loaded?(mod) do
|
||||
Logger.busy(3, "Applying sync_cmd (#{mod}: delete)")
|
||||
Logger.debug(3, "Applying sync_cmd (#{mod}: delete)")
|
||||
|
||||
case repo.get(mod, id) do
|
||||
nil ->
|
||||
|
@ -319,7 +319,7 @@ defmodule Farmbot.Repo do
|
|||
mod = Module.concat(["Farmbot", "Repo", kind])
|
||||
|
||||
if Code.ensure_loaded?(mod) do
|
||||
Logger.busy(3, "Applying sync_cmd (#{mod}): insert_or_update")
|
||||
Logger.debug(3, "Applying sync_cmd (#{mod}): insert_or_update")
|
||||
|
||||
# We need to check if this object exists in the database.
|
||||
case repo.get(mod, id) do
|
||||
|
@ -367,23 +367,65 @@ defmodule Farmbot.Repo do
|
|||
end
|
||||
|
||||
defp do_sync_both(repo_a, repo_b) do
|
||||
case do_sync_all_resources(repo_a) do
|
||||
:ok ->
|
||||
do_sync_all_resources(repo_b)
|
||||
|
||||
err ->
|
||||
err
|
||||
end
|
||||
{time, res} = :timer.tc(fn() ->
|
||||
with {:ok, cache} <- do_http_requests(),
|
||||
:ok <- do_sync_all_resources(repo_a, cache),
|
||||
:ok <- do_sync_all_resources(repo_b, cache) do
|
||||
Farmbot.Bootstrap.SettingsSync.run()
|
||||
end
|
||||
end)
|
||||
Logger.debug 3, "Entire sync took: #{time}µs."
|
||||
res
|
||||
end
|
||||
|
||||
defp do_sync_all_resources(repo) do
|
||||
with :ok <- sync_resource(repo, Device, "/api/device"),
|
||||
:ok <- sync_resource(repo, FarmEvent, "/api/farm_events"),
|
||||
:ok <- sync_resource(repo, Peripheral, "/api/peripherals"),
|
||||
:ok <- sync_resource(repo, Point, "/api/points"),
|
||||
:ok <- sync_resource(repo, Regimen, "/api/regimens"),
|
||||
:ok <- sync_resource(repo, Sequence, "/api/sequences"),
|
||||
:ok <- sync_resource(repo, Tool, "/api/tools") do
|
||||
defp do_http_requests do
|
||||
initial_err = {:error, :request_not_started}
|
||||
acc = %{
|
||||
Device => initial_err,
|
||||
FarmEvent => initial_err,
|
||||
Peripheral => initial_err,
|
||||
Point => initial_err,
|
||||
Regimen => initial_err,
|
||||
Sequence => initial_err,
|
||||
Tool => initial_err
|
||||
}
|
||||
|
||||
device_task = Task.async(__MODULE__, :do_get_resource, [Device, "/api/device"])
|
||||
farm_events_task = Task.async(__MODULE__, :do_get_resource, [FarmEvent, "/api/farm_events"])
|
||||
peripherals_task = Task.async(__MODULE__, :do_get_resource, [Peripheral, "/api/peripherals"])
|
||||
points_task = Task.async(__MODULE__, :do_get_resource, [Point, "/api/points"])
|
||||
regimens_task = Task.async(__MODULE__, :do_get_resource, [Regimen, "/api/regimens"])
|
||||
sequences_task = Task.async(__MODULE__, :do_get_resource, [Sequence, "/api/sequences"])
|
||||
tools_task = Task.async(__MODULE__, :do_get_resource, [Tool, "/api/tools"])
|
||||
res = %{acc |
|
||||
Device => Task.await(device_task, 30_000),
|
||||
FarmEvent => Task.await(farm_events_task, 30_000),
|
||||
Peripheral => Task.await(peripherals_task, 30_000),
|
||||
Point => Task.await(points_task, 30_000),
|
||||
Regimen => Task.await(regimens_task, 30_000),
|
||||
Sequence => Task.await(sequences_task, 30_000),
|
||||
Tool => Task.await(tools_task, 30_000),
|
||||
}
|
||||
{:ok, res}
|
||||
end
|
||||
|
||||
def do_get_resource(resource, slug) do
|
||||
resource = Module.split(resource) |> List.last()
|
||||
Logger.debug 3, "Fetching #{resource}"
|
||||
maybe_debug_log("[#{resource}] Downloading: (#{slug})")
|
||||
{time, res} = :timer.tc(fn -> Farmbot.HTTP.get(slug) end)
|
||||
maybe_debug_log("[#{resource}] HTTP Request took: #{time}µs")
|
||||
res
|
||||
end
|
||||
|
||||
defp do_sync_all_resources(repo, cache) do
|
||||
with :ok <- sync_resource(repo, Device, cache),
|
||||
:ok <- sync_resource(repo, FarmEvent, cache),
|
||||
:ok <- sync_resource(repo, Peripheral, cache),
|
||||
:ok <- sync_resource(repo, Point, cache),
|
||||
:ok <- sync_resource(repo, Regimen, cache),
|
||||
:ok <- sync_resource(repo, Sequence, cache),
|
||||
:ok <- sync_resource(repo, Tool, cache) do
|
||||
:ok
|
||||
else
|
||||
err ->
|
||||
|
@ -392,13 +434,17 @@ defmodule Farmbot.Repo do
|
|||
end
|
||||
end
|
||||
|
||||
defp sync_resource(repo, resource, slug) do
|
||||
Logger.debug(3, "syncing: #{resource} (#{slug})")
|
||||
defp sync_resource(repo, resource, cache) do
|
||||
human_readable_resource_name = Module.split(resource) |> List.last()
|
||||
maybe_debug_log("[#{human_readable_resource_name}] Entering into DB.")
|
||||
as = if resource in @singular_resources, do: struct(resource), else: [struct(resource)]
|
||||
|
||||
with {:ok, %{status_code: 200, body: body}} <- Farmbot.HTTP.get(slug),
|
||||
{:ok, obj_or_list} <- Poison.decode(body, as: as) do
|
||||
case do_insert_or_update(repo, obj_or_list) do
|
||||
with {:ok, %{status_code: 200, body: body}} <- cache[resource],
|
||||
{json_time, {:ok, obj_or_list}} <- :timer.tc(fn -> Poison.decode(body, as: as) end) do
|
||||
maybe_debug_log("[#{human_readable_resource_name}] JSON Decode took: #{json_time}µs")
|
||||
{insert_time, res} = :timer.tc(fn -> do_insert_or_update(repo, obj_or_list) end)
|
||||
maybe_debug_log("[#{human_readable_resource_name}] DB Operations took: #{insert_time}µs")
|
||||
case res do
|
||||
{:ok, _} when resource in @singular_resources -> :ok
|
||||
:ok -> :ok
|
||||
err -> err
|
||||
|
@ -450,6 +496,22 @@ defmodule Farmbot.Repo do
|
|||
end
|
||||
end
|
||||
|
||||
def enable_debug_logs do
|
||||
Application.put_env(:farmbot, :repo_debug_logs, true)
|
||||
end
|
||||
|
||||
def disable_debug_logs do
|
||||
Application.put_env(:farmbot, :repo_debug_logs, false)
|
||||
end
|
||||
|
||||
defp maybe_debug_log(msg) do
|
||||
if Application.get_env(:farmbot, :repo_debug_logs, false) do
|
||||
Logger.debug 3, msg
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
|
|
|
@ -26,6 +26,7 @@ defmodule Farmbot.System.ConfigStorage.Dispatcher do
|
|||
end
|
||||
|
||||
def handle_call({:dispatch, group, key, val}, _, state) do
|
||||
Farmbot.System.Registry.dispatch(:config_storage, {group, key, val})
|
||||
{:reply, :ok, [{:config, group, key, val}], state}
|
||||
end
|
||||
end
|
||||
|
|
39
lib/farmbot/system/profile.ex
Normal file
39
lib/farmbot/system/profile.ex
Normal file
|
@ -0,0 +1,39 @@
|
|||
defmodule Farmbot.System.Profile do
|
||||
@moduledoc File.read!("docs/PROFILES.md")
|
||||
use GenServer
|
||||
use Farmbot.Logger
|
||||
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, [], [name: __MODULE__])
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
profile = Application.get_env(:farmbot, :profile) || System.get_env("FBOS_PROFILE")
|
||||
if profile do
|
||||
try do
|
||||
do_load_profiles(profile)
|
||||
|
||||
rescue
|
||||
error ->
|
||||
IO.warn "Failed to load profile #{profile}: #{inspect Exception.message(error)}\n\n"
|
||||
end
|
||||
end
|
||||
:ignore
|
||||
end
|
||||
|
||||
def profile_dir do
|
||||
case Farmbot.Project.target() do
|
||||
"host" -> Path.join(["overlay", "profiles"])
|
||||
_ -> "/profiles"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_load_profiles(bin) do
|
||||
profiles = String.split(bin, ",")
|
||||
for profile <- profiles do
|
||||
Logger.busy 1, "Loading profile: #{profile}"
|
||||
Code.eval_file("#{profile}.exs", profile_dir())
|
||||
Logger.success 1, "Profile #{profile} loaded."
|
||||
end
|
||||
end
|
||||
end
|
31
lib/farmbot/system/registry.ex
Normal file
31
lib/farmbot/system/registry.ex
Normal file
|
@ -0,0 +1,31 @@
|
|||
defmodule Farmbot.System.Registry do
|
||||
@moduledoc "Farmbot System Global Registry"
|
||||
@reg FarmbotRegistry
|
||||
|
||||
@doc false
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, [], [name: __MODULE__])
|
||||
end
|
||||
|
||||
@doc "Dispatch a global event from a namespace."
|
||||
def dispatch(namespace, event) do
|
||||
GenServer.call(__MODULE__, {:dispatch, namespace, event})
|
||||
end
|
||||
|
||||
def subscribe(pid) do
|
||||
Elixir.Registry.register(@reg, __MODULE__, pid)
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
opts = [keys: :duplicate, partitions: System.schedulers_online, name: @reg]
|
||||
{:ok, reg} = Elixir.Registry.start_link(opts)
|
||||
{:ok, %{reg: reg}}
|
||||
end
|
||||
|
||||
def handle_call({:dispatch, ns, event}, _from, state) do
|
||||
Elixir.Registry.dispatch(@reg, __MODULE__, fn(entries) ->
|
||||
for {pid, _} <- entries, do: send(pid, {__MODULE__, {ns, event}})
|
||||
end)
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
end
|
|
@ -12,12 +12,14 @@ defmodule Farmbot.System.Supervisor do
|
|||
|
||||
def init([]) do
|
||||
before_init_children = [
|
||||
worker(Farmbot.System.Registry, []),
|
||||
worker(Farmbot.System.Init.KernelMods, [[], []]),
|
||||
worker(Farmbot.System.Init.FSCheckup, [[], []]),
|
||||
supervisor(Farmbot.System.Init.Ecto, [[], []]),
|
||||
supervisor(Farmbot.System.ConfigStorage, []),
|
||||
worker(Farmbot.System.ConfigStorage.Dispatcher, []),
|
||||
worker(Farmbot.System.GPIO.Leds, [])
|
||||
worker(Farmbot.System.GPIO.Leds, []),
|
||||
worker(Farmbot.System.Profile, [])
|
||||
]
|
||||
|
||||
init_mods =
|
||||
|
|
|
@ -25,13 +25,24 @@ defmodule Farmbot.System.UpdateTimer do
|
|||
|
||||
def init([]) do
|
||||
spawn __MODULE__, :wait_for_http, [self()]
|
||||
{:ok, [], :hibernate}
|
||||
Farmbot.System.Registry.subscribe(self())
|
||||
{:ok, []}
|
||||
end
|
||||
|
||||
def handle_info(:checkup, state) do
|
||||
osau = Farmbot.System.ConfigStorage.get_config_value(:bool, "settings", "os_auto_update")
|
||||
Farmbot.System.Updates.check_updates(osau)
|
||||
Process.send_after(self(), :checkup, @twelve_hours)
|
||||
{:noreply, state, :hibernate}
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({Farmbot.System.Registry, {:config_storage, {"settings", "beta_opt_in", true}}}, state) do
|
||||
Logger.debug 3, "Opted into beta updates. Refreshing token."
|
||||
Farmbot.Bootstrap.AuthTask.force_refresh()
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({Farmbot.System.Registry, _info}, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -82,7 +82,6 @@ defmodule Farmbot.System.Updates do
|
|||
|
||||
end
|
||||
|
||||
|
||||
if should_apply_update(@env, prerelease, needs_update) do
|
||||
Logger.busy 1, "Downloading FarmbotOS over the air update"
|
||||
IO.puts cl
|
||||
|
@ -103,6 +102,10 @@ defmodule Farmbot.System.Updates do
|
|||
{:error, reason} ->
|
||||
Logger.error 1, "Failed to fetch update data: #{inspect reason}"
|
||||
|
||||
{:ok, %{status_code: 400}} ->
|
||||
Logger.info 2, "Had out of date token. Try that again."
|
||||
Farmbot.Bootstrap.AuthTask.force_refresh()
|
||||
|
||||
{:ok, %{body: body, status_code: code}} ->
|
||||
reason = case Poison.decode(body) do
|
||||
{:ok, res} -> res
|
||||
|
|
19
mix.exs
19
mix.exs
|
@ -27,21 +27,21 @@ defmodule Farmbot.Mixfile do
|
|||
app: :farmbot,
|
||||
description: "The Brains of the Farmbot Project",
|
||||
package: package(),
|
||||
compilers: compilers(),
|
||||
make_clean: ["clean"],
|
||||
make_env: make_env(),
|
||||
compilers: [:elixir_make] ++ Mix.compilers,
|
||||
test_coverage: [tool: ExCoveralls],
|
||||
version: @version,
|
||||
target: @target,
|
||||
commit: commit(),
|
||||
arduino_commit: arduino_commit(),
|
||||
archives: [nerves_bootstrap: "~> 0.7.0"],
|
||||
archives: [nerves_bootstrap: "~> 0.8.0"],
|
||||
build_embedded: Mix.env() == :prod,
|
||||
start_permanent: Mix.env() == :prod,
|
||||
deps_path: "deps/#{@target}",
|
||||
build_path: "_build/#{@target}",
|
||||
lockfile: "mix.lock.#{@target}",
|
||||
config_path: "config/config.exs",
|
||||
lockfile: "mix.lock",
|
||||
elixirc_paths: elixirc_paths(Mix.env(), @target),
|
||||
aliases: aliases(Mix.env(), @target),
|
||||
deps: deps() ++ deps(@target),
|
||||
|
@ -81,11 +81,14 @@ defmodule Farmbot.Mixfile do
|
|||
]
|
||||
end
|
||||
|
||||
defp compilers do
|
||||
case :init.get_plain_arguments() |> List.last() do
|
||||
a when a in ['mix', 'compile', 'firmware'] ->
|
||||
[:elixir_make] ++ Mix.compilers
|
||||
_ -> Mix.compilers
|
||||
defp make_env do
|
||||
case System.get_env("ERL_EI_INCLUDE_DIR") do
|
||||
nil ->
|
||||
%{
|
||||
"ERL_EI_INCLUDE_DIR" => "#{:code.root_dir()}/usr/include",
|
||||
"ERL_EI_LIBDIR" => "#{:code.root_dir()}/usr/lib"
|
||||
}
|
||||
_ -> %{}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ defmodule Farmbot.Target.Network.Manager do
|
|||
alias Farmbot.System.ConfigStorage
|
||||
alias Nerves.Network
|
||||
alias Farmbot.Target.Network.Ntp
|
||||
import Farmbot.Target.Network, only: [test_dns: 0]
|
||||
import Farmbot.Target.Network, only: [test_dns: 0, test_dns: 1]
|
||||
|
||||
def get_ip_addr(interface) do
|
||||
GenServer.call(:"#{__MODULE__}-#{interface}", :ip)
|
||||
|
@ -22,11 +22,11 @@ defmodule Farmbot.Target.Network.Manager do
|
|||
init(args)
|
||||
end
|
||||
SystemRegistry.register()
|
||||
{:ok, _} = Registry.register(Nerves.NetworkInterface, interface, [])
|
||||
{:ok, _} = Registry.register(Nerves.Udhcpc, interface, [])
|
||||
{:ok, _} = Registry.register(Nerves.WpaSupplicant, interface, [])
|
||||
{:ok, _} = Elixir.Registry.register(Nerves.NetworkInterface, interface, [])
|
||||
{:ok, _} = Elixir.Registry.register(Nerves.Udhcpc, interface, [])
|
||||
{:ok, _} = Elixir.Registry.register(Nerves.WpaSupplicant, interface, [])
|
||||
Network.setup(interface, opts)
|
||||
{:ok, %{interface: interface, ip_address: nil, connected: false, not_found_timer: nil, ntp_timer: nil}}
|
||||
{:ok, %{interface: interface, ip_address: nil, connected: false, not_found_timer: nil, ntp_timer: nil, dns_timer: nil}}
|
||||
end
|
||||
|
||||
def handle_call(:ip, _, state) do
|
||||
|
@ -45,8 +45,9 @@ defmodule Farmbot.Target.Network.Manager do
|
|||
|
||||
connected = match?({:ok, {:hostent, 'nerves-project.org', [], :inet, 4, _}}, test_dns())
|
||||
if connected do
|
||||
not_found_timer = cancel_not_found_timer(state.not_found_timer)
|
||||
{:noreply, %{state | ip_address: ip, connected: true, not_found_timer: not_found_timer, ntp_timer: ntp_timer}}
|
||||
not_found_timer = cancel_timer(state.not_found_timer)
|
||||
dns_timer = restart_dns_timer(state.dns_timer, 45_000)
|
||||
{:noreply, %{state | dns_timer: dns_timer, ip_address: ip, connected: true, not_found_timer: not_found_timer, ntp_timer: ntp_timer}}
|
||||
else
|
||||
{:noreply, %{state | connected: false, ntp_timer: ntp_timer, ip_address: ip}}
|
||||
end
|
||||
|
@ -98,24 +99,46 @@ defmodule Farmbot.Target.Network.Manager do
|
|||
{:noreply, %{state | ntp_timer: new_timer}}
|
||||
end
|
||||
|
||||
def handle_info(:dns_timer, state) do
|
||||
case test_dns('my.farmbot.io') do
|
||||
{:ok, {:hostent, _host_name, aliases, :inet, 4, _}} ->
|
||||
# If we weren't previously connected, send a log.
|
||||
unless state.connected do
|
||||
Logger.success 3, "Farmbot was reconnected to the internet: #{inspect aliases}"
|
||||
Farmbot.System.Registry.dispatch(:network, :dns_up)
|
||||
end
|
||||
{:noreply, %{state | connected: true, dns_timer: restart_dns_timer(nil, 45_000)}}
|
||||
{:error, err} ->
|
||||
Farmbot.System.Registry.dispatch(:network, :dns_down)
|
||||
Logger.warn 3, "Farmbot was disconnected from the internet: #{inspect err}"
|
||||
{:noreply, %{state | connected: false, dns_timer: restart_dns_timer(nil, 10_000)}}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(_event, state) do
|
||||
# Logger.warn 3, "unhandled network event: #{inspect event}"
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
defp cancel_not_found_timer(timer) do
|
||||
defp cancel_timer(timer) do
|
||||
# If there was a timer, cancel it.
|
||||
if timer do
|
||||
Logger.warn 3, "Cancelling Network timer"
|
||||
# Logger.warn 3, "Cancelling Network timer"
|
||||
Process.cancel_timer(timer)
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
defp restart_dns_timer(timer, time) when is_number(time) do
|
||||
cancel_timer(timer)
|
||||
Process.send_after(self(), :dns_timer, time)
|
||||
end
|
||||
|
||||
defp maybe_cancel_and_reset_ntp_timer(timer) do
|
||||
if timer do
|
||||
Process.cancel_timer(timer)
|
||||
end
|
||||
|
||||
# introduce a bit of randomness to avoid dosing ntp servers.
|
||||
# I don't think this would ever happen but the default ntpd implementation
|
||||
# does this..
|
||||
|
@ -130,7 +153,6 @@ defmodule Farmbot.Target.Network.Manager do
|
|||
if Farmbot.System.ConfigStorage.get_config_value(:bool, "settings", "first_boot") do
|
||||
Process.send_after(self(), :ntp_timer, 10_000 + rand)
|
||||
else
|
||||
|
||||
Process.send_after(self(), :ntp_timer, 300000 + rand)
|
||||
end
|
||||
end
|
||||
|
|
1
overlay/profiles/.gitignore
vendored
Normal file
1
overlay/profiles/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.exs
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Farmbot.System.ConfigStorage.Migrations.AddApiMigratedFlag do
|
||||
use Ecto.Migration
|
||||
|
||||
import Farmbot.System.ConfigStorage.MigrationHelpers
|
||||
|
||||
def change do
|
||||
create_settings_config("api_migrated", :bool, false)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Farmbot.System.ConfigStorage.Migrations.IgnoreFbosConfig do
|
||||
use Ecto.Migration
|
||||
|
||||
import Farmbot.System.ConfigStorage.MigrationHelpers
|
||||
|
||||
def change do
|
||||
create_settings_config("ignore_fbos_config", :bool, true)
|
||||
end
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
defmodule Farmbot.Bootstrap.AuthorizationTest do
|
||||
@moduledoc "Tests the default authorization implementation"
|
||||
alias Farmbot.Bootstrap.Authorization, as: Auth
|
||||
alias Farmbot.Bootstrap.AuthTask
|
||||
use ExUnit.Case
|
||||
|
||||
@moduletag :farmbot_api
|
||||
|
@ -37,4 +38,15 @@ defmodule Farmbot.Bootstrap.AuthorizationTest do
|
|||
res = Auth.authorize("yolo@mtv.org", "123password", ctx.server)
|
||||
assert match?({:error, _}, res)
|
||||
end
|
||||
|
||||
test "internet off and back on refreshes token" do
|
||||
Farmbot.System.Registry.subscribe(self())
|
||||
old = Farmbot.System.ConfigStorage.get_config_value(:string, "authorization", "token")
|
||||
send AuthTask, {Farmbot.System.Registry, {:network, :dns_up}}
|
||||
|
||||
assert_receive {Farmbot.System.Registry, {:authorization, :new_token}}, 1000
|
||||
|
||||
new = Farmbot.System.ConfigStorage.get_config_value(:string, "authorization", "token")
|
||||
assert old != new
|
||||
end
|
||||
end
|
||||
|
|
44
test/farmbot/bootstrap/settings_sync_test.exs
Normal file
44
test/farmbot/bootstrap/settings_sync_test.exs
Normal file
|
@ -0,0 +1,44 @@
|
|||
defmodule Farmbot.Bootstrap.SettingsSyncTest do
|
||||
use ExUnit.Case, async: false
|
||||
alias Farmbot.Bootstrap.SettingsSync
|
||||
import Farmbot.System.ConfigStorage, only: [update_config_value: 4, get_config_value: 3]
|
||||
|
||||
test "Applies new configs in the form of a map." do
|
||||
SettingsSync.apply_map(%{"firmware_output_log" => true}, %{"firmware_output_log" => false})
|
||||
refute get_config_value(:bool, "settings", "firmware_output_log")
|
||||
|
||||
SettingsSync.apply_map(%{"firmware_hardware" => "arduino"}, %{"firmware_hardware" => "farmduino"})
|
||||
assert get_config_value(:string, "settings", "firmware_hardware") == "farmduino"
|
||||
|
||||
SettingsSync.apply_map(%{"network_not_found_timer" => nil}, %{"network_not_found_timer" => 100})
|
||||
assert get_config_value(:float, "settings", "network_not_found_timer") == 100.0
|
||||
end
|
||||
|
||||
test "doesn't crash on unknown key value pairs when applying a map" do
|
||||
Farmbot.System.Registry.subscribe(self())
|
||||
|
||||
bad_map = %{
|
||||
"some_random_float" => 1.0,
|
||||
"some_random_string" => "hello world",
|
||||
"some_random_bool" => false
|
||||
}
|
||||
SettingsSync.apply_map(bad_map, %{})
|
||||
SettingsSync.apply_map(%{}, bad_map)
|
||||
refute_receive {Farmbot.System.Registry, {:config_storage, {"settings", "some_random_float", _}}}
|
||||
refute_receive {Farmbot.System.Registry, {:config_storage, {"settings", "some_random_string", _}}}
|
||||
refute_receive {Farmbot.System.Registry, {:config_storage, {"settings", "some_random_bool", _}}}
|
||||
end
|
||||
|
||||
test "Updating configs externally will update in fbos" do
|
||||
Farmbot.System.Registry.subscribe(self())
|
||||
config_bin = %{
|
||||
"os_auto_update" => true
|
||||
} |> Poison.encode!
|
||||
update_config_value(:bool, "settings", "os_auto_update", false)
|
||||
assert_receive {Farmbot.System.Registry, {:config_storage, {"settings", "os_auto_update", false}}}
|
||||
|
||||
%{status_code: 200} = Farmbot.HTTP.put!("/api/fbos_config", config_bin)
|
||||
Farmbot.Bootstrap.SettingsSync.run()
|
||||
assert_receive {Farmbot.System.Registry, {:config_storage, {"settings", "os_auto_update", true}}}, 2000
|
||||
end
|
||||
end
|
|
@ -1,81 +1,9 @@
|
|||
defmodule Farmbot.CeleryScript.AST.Node.ConfigUpdateTest do
|
||||
alias Farmbot.CeleryScript.AST.Node.{ConfigUpdate, Pair}
|
||||
|
||||
alias Farmbot.CeleryScript.AST.Node.ConfigUpdate
|
||||
use FarmbotTestSupport.AST.NodeTestCase, async: false
|
||||
alias Farmbot.System.ConfigStorage
|
||||
|
||||
test "mutates env", %{env: env} do
|
||||
{:ok, env} = ConfigUpdate.execute(%{package: :farmbot_os}, [], env)
|
||||
assert_cs_env_mutation(ConfigUpdate, env)
|
||||
end
|
||||
|
||||
test "sets network_not_found_timer", %{env: env} do
|
||||
ConfigUpdate.execute(%{package: :farmbot_os}, pair("network_not_found_timer", 1000), env)
|
||||
|> assert_cs_success()
|
||||
|
||||
assert ConfigStorage.get_config_value(:float, "settings", "network_not_found_timer") == 1000.0
|
||||
end
|
||||
|
||||
test "Wont set network_not_found_timer to a negative number", %{env: env} do
|
||||
old = ConfigStorage.get_config_value(:float, "settings", "network_not_found_timer")
|
||||
ConfigUpdate.execute(%{package: :farmbot_os}, pair("network_not_found_timer", -1), env)
|
||||
|> assert_cs_fail("network_not_found_timer must be greater than zero")
|
||||
|
||||
assert ConfigStorage.get_config_value(:float, "settings", "network_not_found_timer") == old
|
||||
end
|
||||
|
||||
test "sets os auto update", %{env: env} do
|
||||
ConfigUpdate.execute(%{package: :farmbot_os}, pair("os_auto_update", true), env) |> assert_cs_success()
|
||||
assert ConfigStorage.get_config_value(:bool, "settings", "os_auto_update")
|
||||
|
||||
ConfigUpdate.execute(%{package: :farmbot_os}, pair("os_auto_update", false), env) |> assert_cs_success()
|
||||
refute ConfigStorage.get_config_value(:bool, "settings", "os_auto_update")
|
||||
end
|
||||
|
||||
test "sets auto sync", %{env: env} do
|
||||
ConfigUpdate.execute(%{package: :farmbot_os}, pair("auto_sync", true), env) |> assert_cs_success()
|
||||
assert ConfigStorage.get_config_value(:bool, "settings", "auto_sync")
|
||||
|
||||
ConfigUpdate.execute(%{package: :farmbot_os}, pair("auto_sync", false), env) |> assert_cs_success()
|
||||
refute ConfigStorage.get_config_value(:bool, "settings", "auto_sync")
|
||||
end
|
||||
|
||||
test "can not set arduino hardware to unknown setting", %{env: env} do
|
||||
ConfigUpdate.execute(%{package: :farmbot_os}, pair("firmware_hardware", "whoops"), env)
|
||||
|> assert_cs_fail("unknown hardware: whoops")
|
||||
|
||||
refute ConfigStorage.get_config_value(:string, "settings", "firmware_hardware") == "whoops"
|
||||
end
|
||||
|
||||
test "gives decent error on unknown onfig files.", %{env: env} do
|
||||
ConfigUpdate.execute(%{package: :farmbot_os}, pair("some_other_config", "whoops"), env)
|
||||
|> assert_cs_fail("unknown config: some_other_config")
|
||||
end
|
||||
|
||||
test "can set float values with an integer", %{env: env} do
|
||||
ConfigUpdate.execute(%{package: :farmbot_os}, pair("network_not_found_timer", 1000), env)
|
||||
|> assert_cs_success()
|
||||
end
|
||||
|
||||
test "can set float values with a float", %{env: env} do
|
||||
ConfigUpdate.execute(%{package: :farmbot_os}, pair("network_not_found_timer", 1000.00), env)
|
||||
|> assert_cs_success()
|
||||
end
|
||||
|
||||
test "allows setting multiple configs", %{env: env} do
|
||||
pairs = pairs([{"network_not_found_timer", 10.0}, {"firmware_hardware", "farmduino"}])
|
||||
ConfigUpdate.execute(%{package: :farmbot_os}, pairs, env) |> assert_cs_success()
|
||||
|
||||
assert ConfigStorage.get_config_value(:string, "settings", "firmware_hardware") == "farmduino"
|
||||
assert ConfigStorage.get_config_value(:float, "settings", "network_not_found_timer") == 10.0
|
||||
end
|
||||
|
||||
defp pair(key, val) do
|
||||
{:ok, pair, _} = Pair.execute(%{label: key, value: val}, [], struct(Macro.Env, []))
|
||||
[pair]
|
||||
end
|
||||
|
||||
defp pairs(pairs) do
|
||||
Enum.map(pairs, fn({key, val}) -> pair(key, val) end) |> List.flatten()
|
||||
end
|
||||
end
|
||||
|
|
10
test/farmbot/system/registry_test.exs
Normal file
10
test/farmbot/system/registry_test.exs
Normal file
|
@ -0,0 +1,10 @@
|
|||
defmodule Farmbot.System.RegistryTest do
|
||||
use ExUnit.Case
|
||||
alias Farmbot.System.Registry
|
||||
|
||||
test "subscribes and dispatches global events" do
|
||||
Registry.subscribe(self())
|
||||
Registry.dispatch(:hello, :world)
|
||||
assert_receive {Registry, {:hello, :world}}
|
||||
end
|
||||
end
|
19
test/farmbot/system/updates/update_timer_test.exs
Normal file
19
test/farmbot/system/updates/update_timer_test.exs
Normal file
|
@ -0,0 +1,19 @@
|
|||
defmodule Farmbot.System.UpdateTimerTest do
|
||||
use ExUnit.Case, async: false
|
||||
alias Farmbot.System.ConfigStorage
|
||||
|
||||
test "Opting into beta updates should refresh token" do
|
||||
Farmbot.System.Registry.subscribe(self())
|
||||
|
||||
old = ConfigStorage.get_config_value(:string, "authorization", "token")
|
||||
|
||||
ConfigStorage.update_config_value(:bool, "settings", "beta_opt_in", false)
|
||||
ConfigStorage.update_config_value(:bool, "settings", "beta_opt_in", true)
|
||||
|
||||
assert_receive {Farmbot.System.Registry, {:config_storage, {"settings", "beta_opt_in", true}}}
|
||||
assert_receive {Farmbot.System.Registry, {:authorization, :new_token}}, 1000
|
||||
|
||||
new = ConfigStorage.get_config_value(:string, "authorization", "token")
|
||||
assert old != new
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue