Merge pull request #400 from FarmBot/staging

Release Prep
pull/403/head
Connor Rigby 2018-01-04 13:25:26 -08:00 committed by GitHub
commit e43026a8af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 5666 additions and 5330 deletions

View File

@ -1,76 +1,156 @@
version: 2.0
defaults: &defaults
working_directory: ~/farmbot_os
docker:
- image: nervesproject/nerves:0.13.5
environment:
ENV: CI
MIX_ENV: test
ELIXIR_VERSION: 1.5.2
install_elixir: &install_elixir
run:
name: Install Elixir
command: |
wget https://github.com/elixir-lang/elixir/releases/download/v$ELIXIR_VERSION/Precompiled.zip
wget https://github.com/elixir-lang/elixir/releases/download/v1.5.2/Precompiled.zip
unzip -d /usr/local/elixir Precompiled.zip
echo 'export PATH=/usr/local/elixir/bin:$PATH' >> $BASH_ENV
install_hex_rebar: &install_hex_rebar
install_hex_archives: &install_hex_archives
run:
name: Install hex and rebar
name: Install archives
command: |
mix local.hex --force
mix local.rebar --force
install_nerves_bootstrap: &install_nerves_bootstrap
mix archive.install hex nerves_bootstrap --force
fetch_and_compile_deps: &fetch_and_compile_deps
run:
name: Install nerves_bootstrap
command: mix archive.install hex nerves_bootstrap --force
name: Fetch and compile Elixir dependencies
command: |
mix deps.get
mix deps.compile
mix compile
jobs:
test:
<<: *defaults
environment:
MIX_ENV: test
MIX_TARGET: host
steps:
- checkout
- <<: *install_elixir
- <<: *install_hex_rebar
- <<: *install_nerves_bootstrap
- restore_cache:
keys:
- v3-dependency-cache-{{ checksum "mix.lock.host" }}
- v3-dependency-cache
- <<: *install_hex_archives
- <<: *fetch_and_compile_deps
- save_cache:
key: v3-dependency-cache-{{ checksum "mix.lock.host" }}
paths:
- _build
- ~/.mix
- ~/.nerves
- run:
name: Install test dependencies
command: mix deps.get
- run:
name: Compile
command: mix compile
- run:
name: Lint
command: mix credo --strict --ignore Credo.Check.Readability.MaxLineLength
- run:
name: Test
command: mix coveralls.circle --seed 0 --exclude farmbot_firmware
command: mix coveralls.circle --exclude farmbot_firmware
firmware:
firmware_dev:
<<: *defaults
environment:
MIX_ENV: dev
MIX_TARGET: rpi3
ELIXIR_VERSION: 1.5.2
MIX_ENV: dev
ENV: CI
steps:
- checkout
- <<: *install_elixir
- <<: *install_hex_rebar
- <<: *install_nerves_bootstrap
- restore_cache:
keys:
- v3-dependency-cache-{{ checksum "mix.lock.rpi3" }}
- v3-dependency-cache
- <<: *install_hex_archives
- <<: *fetch_and_compile_deps
- run: mix firmware
- save_cache:
key: v3-dependency-cache-{{ checksum "mix.lock.rpi3" }}
paths:
- _build
- ~/.mix
- ~/.nerves
- run: mix firmware.slack --channels C58DCU4A3
- run: mkdir -p artifacts
- run:
name: Set COMMIT_MESSAGE var
command: echo "export COMMIT_MESSAGE=\"$(git log --format=oneline -n 1 $CIRCLE_SHA1)\"" >> ~/.bashrc
name: Decode fwup priv key
command: echo $FWUP_KEY_BASE64 | base64 --decode --ignore-garbage > $NERVES_FW_PRIV_KEY
- run:
name: Install dev dependencies
command: mix deps.get
name: Sign firmware
command: fwup -S -s $NERVES_FW_PRIV_KEY -i _build/${MIX_TARGET}/${MIX_ENV}/nerves/images/farmbot.fw -o artifacts/farmbot-${MIX_TARGET}-$(cat VERSION)-beta.fw
- save_cache:
key: v3-firmware-{{ .Revision }}-{{ .Environment.CIRCLE_TAG }}
paths:
- ./artifacts
firmware_prod:
<<: *defaults
environment:
MIX_TARGET: rpi3
MIX_ENV: prod
ENV: CI
steps:
- checkout
- <<: *install_elixir
- restore_cache:
keys:
- v3-dependency-cache-{{ checksum "mix.lock.rpi3" }}
- v3-dependency-cache
- <<: *install_hex_archives
- run: mix deps.get
- run: mix deps.compile
- run: mix compile
- run: mix firmware
- save_cache:
key: v3-dependency-cache-{{ checksum "mix.lock.rpi3" }}
paths:
- _build
- ~/.mix
- ~/.nerves
- run: mkdir -p artifacts
- run:
name: Build dev firmware
command: mix firmware
name: Decode fwup priv key
command: echo $FWUP_KEY_BASE64 | base64 --decode --ignore-garbage > $NERVES_FW_PRIV_KEY
- run:
name: Upload dev firmware to slack
command: mix firmware.slack --channels C58DCU4A3 $COMMIT_MESSAGE
name: Sign firmware
command: fwup -S -s $NERVES_FW_PRIV_KEY -i _build/${MIX_TARGET}/${MIX_ENV}/nerves/images/farmbot.fw -o artifacts/farmbot-${MIX_TARGET}-$(cat VERSION)-beta.fw
- save_cache:
key: v3-firmware-{{ .Revision }}-{{ .Environment.CIRCLE_TAG }}
paths:
- ./artifacts
deploy_firmware:
<<: *defaults
steps:
- checkout
- run:
name: Run setup script
command: bash .circleci/setup-heroku.sh
- add_ssh_keys:
fingerprints:
- "97:92:32:5d:d7:96:e1:fa:f3:6b:f3:bd:d6:aa:84:c6"
- run:
name: Install dependencies
command: |
wget https://github.com/tcnksm/ghr/releases/download/v0.5.4/ghr_v0.5.4_linux_amd64.zip
unzip ghr_v0.5.4_linux_amd64.zip
wget https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64
chmod +x ./jq-linux64
- run:
command: grep -Pazo "(?s)(?<=# $(cat VERSION))[^#]+" CHANGELOG.md > RELEASE_NOTES
- restore_cache:
key: v3-firmware-{{ .Revision }}-{{ .Environment.CIRCLE_TAG }}
- run:
command: ./ghr -t $GITHUB_TOKEN -u farmbot -r farmbot_os -recreate -prerelease -b "$(cat RELEASE_NOTES)" -c $(git rev-parse --verify HEAD) "v$(cat VERSION)-beta" $PWD/artifacts
- run:
name: Update heroku env
command: |
export OTA_URL=$(wget https://api.github.com/repos/farmbot/farmbot_os/releases -qO- | ./jq-linux64 '.[0].url')
heroku config:set BETA_OTA_URL=$OTA_URL --app=farmbot-production
heroku config:set BETA_OTA_URL=$OTA_URL --app=farmbot-staging
workflows:
version: 2
@ -78,5 +158,30 @@ workflows:
jobs:
- test:
context: org-global
- firmware:
filters:
branches:
ignore: beta
- firmware_dev:
context: org-global
requires:
- test
filters:
branches:
ignore: beta
deploy_beta:
jobs:
- firmware_prod:
context: org-global
filters:
branches:
only:
- beta
- deploy_firmware:
context: org-global
filters:
branches:
only:
- beta
requires:
- firmware_prod

View File

@ -0,0 +1,16 @@
#!/bin/bash
wget https://cli-assets.heroku.com/branches/stable/heroku-linux-amd64.tar.gz
mkdir -p /usr/local/lib /usr/local/bin
tar -xvzf heroku-linux-amd64.tar.gz -C /usr/local/lib
ln -s /usr/local/lib/heroku/bin/heroku /usr/local/bin/heroku
cat > ~/.netrc << EOF
machine api.heroku.com
login $HEROKU_LOGIN
password $HEROKU_API_KEY
EOF
cat >> ~/.ssh/config << EOF
VerifyHostKeyDNS yes
StrictHostKeyChecking no
EOF

View File

@ -1,2 +1,2 @@
erlang 20.0
erlang 20.1
elixir 1.5.2

View File

@ -113,3 +113,21 @@
# 5.0.9
* Add missing redis-py package for Farmware.
# 6.0.1
* Add feature auto sync.
* Add feature RPI GPIO.
* Refactor Configurator to not need Javascript/Webpack
* Add timer before network not found factory resets bot.
* Remove steps/mm conversion.
* Bundle new arduino-firmware.
* Replace MQTT with AMQP.
* Get rid of Log batching.
* Add verbosity level to _every_ log message.
* Show position for log messages.
* Add many helpful log messages.
* Add feature to disable many log message.
* Add feature to log all arduino-firmware I/O.
* Migrated CI to CircleCI from TravisCI.
* Refactored FarmEvent Calendar generator.
* Fix a ton of little bugs.

View File

@ -64,12 +64,6 @@ config :farmbot, :behaviour,
update_handler: Farmbot.Target.UpdateHandler,
gpio_handler: Farmbot.Target.GPIO.AleHandler
config :nerves_firmware_ssh,
authorized_keys: [
File.read!(Path.join(System.user_home!, ".ssh/id_rsa.pub"))
]
config :nerves_init_gadget,
address_method: :static

View File

@ -24,7 +24,9 @@ defmodule Farmbot.Bootstrap.AuthTask do
end
def terminate(reason, _state) do
Logger.error 1, "Token Refresh failed: #{inspect reason}"
unless reason == {:shutdown, :normal} do
Logger.error 1, "Token Refresh failed: #{inspect reason}"
end
end
def handle_info(:refresh, _old_timer) do

View File

@ -49,7 +49,8 @@ defmodule Farmbot.BotState.Transport.AMQP do
opts <- [conn: conn, chan: chan, queue_name: q_name, bot: device],
state <- struct(State, opts)
do
Process.monitor(conn.pid)
true = Process.link(conn.pid)
true = Process.link(chan.pid)
{:consumer, state, subscribe_to: [Farmbot.BotState, Farmbot.Logger]}
else
{:error, {:auth_failure, msg}} = fail ->
@ -146,10 +147,6 @@ defmodule Farmbot.BotState.Transport.AMQP do
{:noreply, [], state}
end
def handle_info({:DOWN, _, :process, _pid, reason}, state) do
{:stop, reason, state}
end
# Confirmation sent by the broker after registering this process as a consumer
def handle_info({:basic_consume_ok, _}, state) do
if get_config_value(:bool, "settings", "log_amqp_connected") do
@ -191,14 +188,27 @@ defmodule Farmbot.BotState.Transport.AMQP do
end
end
defp handle_celery_script(payload, _state) do
def handle_info({:DOWN, _, :process, pid, reason}, state) do
unless reason == :normal do
Logger.warn 3, "CeleryScript: #{inspect pid} died: #{inspect reason}"
end
{:noreply, [], state}
end
@doc false
def handle_celery_script(payload, _state) do
case AST.decode(payload) do
{:ok, ast} -> spawn CeleryScript, :execute, [ast]
{:ok, ast} ->
pid = spawn(CeleryScript, :execute, [ast])
# Logger.busy 3, "CeleryScript starting: #{inspect pid}"
Process.monitor(pid)
:ok
_ -> :ok
end
end
defp handle_sync_cmd(kind, id, payload, state) do
@doc false
def handle_sync_cmd(kind, id, payload, state) do
mod = Module.concat(["Farmbot", "Repo", kind])
if Code.ensure_loaded?(mod) do
%{
@ -206,13 +216,13 @@ defmodule Farmbot.BotState.Transport.AMQP do
"args" => %{"label" => uuid}
} = Poison.decode!(payload, as: %{"body" => struct(mod)})
Farmbot.Repo.register_sync_cmd(String.to_integer(id), kind, body)
:ok = Farmbot.Repo.register_sync_cmd(String.to_integer(id), kind, body)
if get_config_value(:bool, "settings", "auto_sync") do
Farmbot.Repo.flip()
end
AST.Node.RpcOk.execute(%{label: uuid}, [], struct(Macro.Env))
{:ok, %Macro.Env{}} = AST.Node.RpcOk.execute(%{label: uuid}, [], struct(Macro.Env))
else
msg = "Unknown syncable: #{mod}: #{inspect Poison.decode!(payload)}"
Logger.warn 2, msg

View File

@ -5,9 +5,10 @@ defmodule Farmbot.CeleryScript.AST.Node.CheckUpdates do
def execute(%{package: :farmbot_os}, _, env) do
env = mutate_env(env)
case Farmbot.System.Updates.check_updates() do
case Farmbot.System.Updates.check_updates(true) do
:ok -> {:ok, env}
{:error, reason} -> {:error, reason, env}
:no_update -> {:ok, env}
_ -> {:error, "Failed to check updates", env}
end
end

View File

@ -59,6 +59,7 @@ defmodule Farmbot.CeleryScript.AST.Node.ConfigUpdate do
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("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"}

View File

@ -7,14 +7,10 @@ defmodule Farmbot.CeleryScript.AST.Node.WritePin do
def execute(%{pin_mode: mode, pin_value: value, pin_number: num}, [], env) do
env = mutate_env(env)
case Farmbot.Firmware.set_pin_mode(num, :output) do
case Farmbot.Firmware.write_pin(num, mode, value) do
:ok ->
case Farmbot.Firmware.write_pin(num, mode, value) do
:ok ->
log_success(num, mode, value)
{:ok, env}
{:error, reason} -> {:error, reason, env}
end
log_success(num, mode, value)
{:ok, env}
{:error, reason} -> {:error, reason, env}
end
end

View File

@ -31,7 +31,7 @@ defmodule Farmbot.CeleryScript.AST.Node.Zero do
@default_num_tries 20
defp do_wait_for_pos(axis, env, tries \\ @default_num_tries)
defp do_wait_for_pos(axis, env, 0) do
defp do_wait_for_pos(_axis, env, 0) do
# {:error, "Failed to set #{axis} location to 0", env}
{:ok, env}
end

View File

@ -68,7 +68,7 @@ defmodule Farmbot.FarmEvent.Manager do
{:noreply, %{state | timer: nil, checkup: checkup}}
end
def handle_info({:DOWN, _, :process, _, {:success, new_state}}, old_state) do
def handle_info({:DOWN, _, :process, _, {:success, new_state}}, _old_state) do
timer = Process.send_after(self(), :checkup, @checkup_time)
{:noreply, %{new_state | timer: timer, checkup: nil}}
end
@ -79,7 +79,7 @@ defmodule Farmbot.FarmEvent.Manager do
{:noreply, %{state | timer: timer, checkup: nil}}
end
def async_checkup(manager, state) do
def async_checkup(_manager, state) do
now = get_now()
alias Farmbot.Repo.FarmEvent
# maybe_farm_event_log "Rebuilding calendar."

View File

@ -4,35 +4,40 @@ defmodule Farmbot.Firmware do
use GenStage
use Farmbot.Logger
alias Farmbot.Firmware.Vec3
import Farmbot.System.ConfigStorage, only: [update_config_value: 4, get_config_value: 3]
# If any command takes longer than this, exit.
@call_timeout 500_000
@doc "Move the bot to a position."
def move_absolute(%Vec3{} = vec3, x_speed, y_speed, z_speed) do
GenStage.call(__MODULE__, {:move_absolute, [vec3, x_speed, y_speed, z_speed]}, :infinity)
def move_absolute(%Vec3{} = vec3, x_spd, y_spd, z_spd) do
call = {:move_absolute, [vec3, x_spd, y_spd, z_spd]}
GenStage.call(__MODULE__, call, @call_timeout)
end
@doc "Calibrate an axis."
def calibrate(axis) do
GenStage.call(__MODULE__, {:calibrate, [axis]}, :infinity)
GenStage.call(__MODULE__, {:calibrate, [axis]}, @call_timeout)
end
@doc "Find home on an axis."
def find_home(axis) do
GenStage.call(__MODULE__, {:find_home, [axis]}, :infinity)
GenStage.call(__MODULE__, {:find_home, [axis]}, @call_timeout)
end
@doc "Home every axis."
def home_all() do
GenStage.call(__MODULE__, {:home_all, []}, :infinity)
GenStage.call(__MODULE__, {:home_all, []}, @call_timeout)
end
@doc "Home an axis."
def home(axis) do
GenStage.call(__MODULE__, {:home, [axis]}, :infinity)
GenStage.call(__MODULE__, {:home, [axis]}, @call_timeout)
end
@doc "Manually set an axis's current position to zero."
def zero(axis) do
GenStage.call(__MODULE__, {:zero, [axis]}, :infinity)
GenStage.call(__MODULE__, {:zero, [axis]}, @call_timeout)
end
@doc """
@ -40,12 +45,12 @@ defmodule Farmbot.Firmware do
For a list of paramaters see `Farmbot.Firmware.Gcode.Param`
"""
def update_param(param, val) do
GenStage.call(__MODULE__, {:update_param, [param, val]}, :infinity)
GenStage.call(__MODULE__, {:update_param, [param, val]}, @call_timeout)
end
@doc false
def read_all_params do
GenStage.call(__MODULE__, {:read_all_params, []}, :infinity)
GenStage.call(__MODULE__, {:read_all_params, []}, @call_timeout)
end
@doc """
@ -53,42 +58,42 @@ defmodule Farmbot.Firmware do
For a list of paramaters see `Farmbot.Firmware.Gcode.Param`
"""
def read_param(param) do
GenStage.call(__MODULE__, {:read_param, [param]}, :infinity)
GenStage.call(__MODULE__, {:read_param, [param]}, @call_timeout)
end
@doc "Emergency lock Farmbot."
def emergency_lock() do
GenStage.call(__MODULE__, {:emergency_lock, []}, :infinity)
GenStage.call(__MODULE__, {:emergency_lock, []}, @call_timeout)
end
@doc "Unlock Farmbot from Emergency state."
def emergency_unlock() do
GenStage.call(__MODULE__, {:emergency_unlock, []}, :infinity)
GenStage.call(__MODULE__, {:emergency_unlock, []}, @call_timeout)
end
@doc "Set a pin mode (:input | :output)"
def set_pin_mode(pin, mode) do
GenStage.call(__MODULE__, {:set_pin_mode, [pin, mode]}, :infinity)
GenStage.call(__MODULE__, {:set_pin_mode, [pin, mode]}, @call_timeout)
end
@doc "Read a pin."
def read_pin(pin, mode) do
GenStage.call(__MODULE__, {:read_pin, [pin, mode]}, :infinity)
GenStage.call(__MODULE__, {:read_pin, [pin, mode]}, @call_timeout)
end
@doc "Write a pin."
def write_pin(pin, mode, value) do
GenStage.call(__MODULE__, {:write_pin, [pin, mode, value]}, :infinity)
GenStage.call(__MODULE__, {:write_pin, [pin, mode, value]}, @call_timeout)
end
@doc "Request version."
def request_software_version do
GenStage.call(__MODULE__, {:request_software_version, []}, :infinity)
GenStage.call(__MODULE__, {:request_software_version, []}, @call_timeout)
end
@doc "Set angle of a servo pin."
def set_servo_angle(pin, value) do
GenStage.call(__MODULE__, {:set_servo_angle, [pin, value]}, :infinity)
GenStage.call(__MODULE__, {:set_servo_angle, [pin, value]}, @call_timeout)
end
@doc "Start the firmware services."
@ -103,8 +108,7 @@ defmodule Farmbot.Firmware do
defstruct [
fun: nil,
args: nil,
from: nil,
from: nil
]
end
@ -114,6 +118,7 @@ defmodule Farmbot.Firmware do
idle: false,
timer: nil,
pins: %{},
params: %{},
initialized: false,
initializing: false,
current: nil,
@ -138,6 +143,15 @@ defmodule Farmbot.Firmware do
end
def terminate(reason, state) do
unless :queue.is_empty(state.queue) do
list = :queue.to_list(state.queue)
for cmd <- list do
:ok = do_reply(%{state | current: cmd}, {:error, reason})
end
end
end
def handle_info({:EXIT, _pid, :normal}, state) do
{:stop, :normal, state}
end
@ -152,59 +166,66 @@ defmodule Farmbot.Firmware do
def handle_info(:timeout, state) do
case state.current do
nil -> {:noreply, [], %{state | timer: nil}}
%Current{fun: fun, args: args, from: from} = current ->
%Current{fun: fun, args: args, from: _from} = current ->
Logger.warn 1, "Got Firmware timeout. Retrying #{fun}(#{inspect args}) "
case apply(state.handler_mod, fun, [state.handler | args]) do
:ok ->
timer = Process.send_after(self(), :timeout, state.timeout_ms)
{:noreply, [], %{state | current: current, timer: timer}}
{:error, _} = res ->
GenStage.reply(from, res)
do_reply(state, res)
{:noreply, [], %{state | current: nil, queue: :queue.new()}}
end
{:noreply, [], %{state | timer: nil}}
end
end
def handle_call({fun, _}, _from, state = %{initialized: false}) when fun not in [:read_all_params, :update_param, :emergency_unlock, :emergency_lock] do
def handle_call({:fetch_param_hack_delete_me, param}, _, state) do
{:reply, Map.get(state.params, param), [], state}
end
def handle_call({fun, _}, _from, state = %{initialized: false}) when fun not in [:read_param, :read_all_params, :update_param, :emergency_unlock, :emergency_lock] do
{:reply, {:error, :uninitialized}, [], state}
end
def handle_call({fun, args}, from, state) do
current = struct(Current, from: from, fun: fun, args: args)
if :queue.is_empty(state.queue) do
do_begin_cmd(current, state, [])
next_current = struct(Current, from: from, fun: fun, args: args)
current_current = state.current
if current_current do
do_queue_cmd(next_current, state)
else
do_queue_cmd(current, state)
do_begin_cmd(next_current, state, [])
end
end
defp do_begin_cmd(%Current{fun: fun, args: args, from: _from} = current, state, dispatch) do
# Logger.debug 3, "Firmware command: #{fun}#{inspect(args)}"
# 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}}
{:error, _} = res ->
{:reply, res, dispatch, %{state | current: nil, queue: :queue.new()}}
do_reply(%{state | current: current}, res)
{:noreply, dispatch, %{state | current: nil}}
end
end
defp do_queue_cmd(%Current{fun: _fun, args: _args, from: _from} = current, state) do
# Logger.busy 3, "FW Queuing: #{fun}: #{inspect from}"
new_q = :queue.in(current, state.queue)
{:noreply, [], %{state | queue: new_q}}
end
def handle_events(gcodes, _from, state) do
{diffs, state} = handle_gcodes(gcodes, state)
if state.current == nil do
# if after handling the current buffer of gcodes,
# Try to start the next command in the queue if it exists.
if List.last(gcodes) == :idle && state.current == nil do
case :queue.out(state.queue) do
{{:value, current}, new_queue} ->
do_begin_cmd(current, %{state | queue: new_queue, current: current}, diffs)
{:empty, queue} ->
{{:value, next_current}, new_queue} ->
do_begin_cmd(next_current, %{state | queue: new_queue, current: next_current}, diffs)
{:empty, queue} -> # nothing to do if the queue is empty.
{:noreply, diffs, %{state | queue: queue}}
end
else
@ -242,7 +263,7 @@ defmodule Farmbot.Firmware do
end
end)
Logger.error 1, "Failed to execute #{state.current.fun} #{inspect formatted_args}"
GenStage.reply(state.current.from, {:error, :firmware_error})
do_reply(state, {:error, :firmware_error})
{nil, %{state | current: nil}}
else
{nil, state}
@ -290,13 +311,17 @@ defmodule Farmbot.Firmware do
defp handle_gcode({:report_parameter_value, param, value}, state) when (value == -1) do
Farmbot.System.ConfigStorage.update_config_value(:float, "hardware_params", to_string(param), nil)
{:mcu_params, %{param => nil}, state}
{:mcu_params, %{param => nil}, %{state | params: Map.put(state.params, param, value)}}
end
defp handle_gcode({:report_parameter_value, param, value}, state) do
defp handle_gcode({:report_parameter_value, param, value}, state) when is_number(value) do
Farmbot.System.ConfigStorage.update_config_value(:float, "hardware_params", to_string(param), value / 1)
{:mcu_params, %{param => value}, %{state | params: Map.put(state.params, param, value)}}
end
{:mcu_params, %{param => value}, state}
defp handle_gcode({:report_parameter_value, param, nil}, state) do
Farmbot.System.ConfigStorage.update_config_value(:float, "hardware_params", to_string(param), nil)
{:mcu_params, %{param => nil}, %{state | params: Map.put(state.params, param, nil)}}
end
defp handle_gcode(:idle, %{initialized: false, initializing: false} = state) do
@ -322,13 +347,13 @@ defmodule Farmbot.Firmware do
Farmbot.BotState.set_busy(false)
if state.current do
# This might be a bug in the FW
if state.current.command in [:home, :home_all] do
if state.current.fun in [:home, :home_all] do
Logger.warn 1, "Got idle during home. Ignoring. This might be bad."
timer = Process.send_after(self(), :timeout, state.timeout_ms)
{nil, %{state | timer: timer}}
else
Logger.warn 1, "Got idle while executing a command."
GenStage.reply(state.current.from, {:error, :timeout})
do_reply(state, {:error, :timeout})
{:informational_settings, %{busy: false, locked: false}, %{state | current: nil, idle: true}}
end
else
@ -379,7 +404,7 @@ defmodule Farmbot.Firmware do
defp handle_gcode(:done, state) do
maybe_cancel_timer(state.timer)
if state.current do
GenStage.reply(state.current.from, :ok)
do_reply(state, :ok)
{nil, %{state | current: nil}}
else
{nil, state}
@ -389,7 +414,7 @@ defmodule Farmbot.Firmware do
defp handle_gcode(:report_emergency_lock, state) do
Farmbot.System.GPIO.Leds.led_status_err
if state.current do
GenStage.reply(state.current.from, {:error, :emergency_lock})
do_reply(state, {:error, :emergency_lock})
{:informational_settings, %{locked: true}, %{state | current: nil}}
else
{:informational_settings, %{locked: true}, state}
@ -437,7 +462,31 @@ defmodule Farmbot.Firmware do
@doc false
def do_read_params_and_report_position(old) when is_map(old) do
for {key, float_val} <- old do
if get_config_value(:bool, "settings", "fw_upgrade_migration") do
Logger.busy(1, "Migrating old configuration data from firmware.")
migration_hack = fn(param_atom) ->
read_param(param_atom)
val = GenServer.call(__MODULE__, {:fetch_param_hack_delete_me, param_atom})
modified = cond do
is_nil(val) -> 5556
val == 56 -> 5556
5556 -> 5556
is_number(val) -> val * 100
true -> 5556
end
Logger.info 2, "Update param: #{param_atom}: #{val} => #{inspect modified}"
update_param(param_atom, modified)
end
for param <- [:encoder_scaling_x, :encoder_scaling_y, :encoder_scaling_z] do
Logger.busy 3, "Migrating: #{inspect param}"
migration_hack.(param)
Logger.success 3, "Done Migrating: #{inspect param}"
end
update_config_value(:bool, "settings", "fw_upgrade_migration", false)
Logger.success(1, "Finished migrating old configuration data.")
end
for {key, float_val} <- Map.drop(old, ["encoder_scaling_x", "encoder_scaling_y", "encoder_scaling_z"]) do
cond do
(float_val == -1) -> :ok
is_nil(float_val) -> :ok
@ -471,4 +520,15 @@ defmodule Farmbot.Firmware do
report_calibration_callback(tries - 1, param, val)
end
end
defp do_reply(state, reply) do
case state.current do
%Current{fun: _fun, from: from} ->
# Logger.success 3, "FW Replying: #{fun}: #{inspect from}"
:ok = GenServer.reply from, reply
nil ->
Logger.error 1, "FW Nothing to send reply: #{inspect reply} to!."
:error
end
end
end

View File

@ -14,8 +14,8 @@ defmodule Farmbot.Firmware.StubHandler do
GenStage.start_link(__MODULE__, [])
end
def move_absolute(handler, pos, x_speed, y_speed, z_speed) do
GenStage.call(handler, {:move_absolute, pos, x_speed, y_speed, z_speed})
def move_absolute(handler, pos, x_spd, y_spd, z_spd) do
GenStage.call(handler, {:move_absolute, pos, x_spd, y_spd, z_spd}, 120_000)
end
def calibrate(handler, axis) do
@ -116,6 +116,7 @@ defmodule Farmbot.Firmware.StubHandler do
end
def handle_call({:move_absolute, pos, _x_speed, _y_speed, _z_speed}, _from, state) do
Process.sleep(5000)
response = build_resp [{:report_current_position, pos.x, pos.y, pos.z},
{:report_encoder_position_scaled, pos.x, pos.y, pos.z},
{:report_encoder_position_raw, pos.x, pos.y, pos.z}, :done]
@ -202,7 +203,7 @@ defmodule Farmbot.Firmware.StubHandler do
def handle_call({:read_param, param}, _from, state) do
res = state.fw_params[param]
response = build_resp [{:report_paramater_value, param, res}, :done]
response = build_resp [{:report_parameter_value, param, res}, :done]
{:reply, build_reply(:ok), response, state}
end

View File

@ -52,7 +52,7 @@ defmodule Farmbot.Logger.Console do
:ok
end
defp maybe_log(%Farmbot.Log{module: module} = log) do
defp maybe_log(%Farmbot.Log{module: _module} = log) do
# should_log = List.first(Module.split(module)) == "Farmbot"
# if should_log do
# credo:disable-for-next-line

View File

@ -8,7 +8,6 @@ defmodule Farmbot.Repo.FarmEvent do
"""
@on_load :load_nif
def load_nif do
require Logger
nif_file = '#{:code.priv_dir(:farmbot)}/build_calendar'

View File

@ -26,24 +26,27 @@ defmodule Farmbot.Repo do
# fifteen seconds.
# @timeout 15000
# 1.5 minutes.
@call_timeout_ms 90_000
@doc "Fetch the current repo."
def current_repo do
GenServer.call(__MODULE__, :current_repo, :infinity)
GenServer.call(__MODULE__, :current_repo)
end
@doc "Fetch the non current repo."
def other_repo do
GenServer.call(__MODULE__, :other_repo, :infinity)
GenServer.call(__MODULE__, :other_repo)
end
@doc "Flip the repos."
def flip() do
GenServer.call(__MODULE__, :flip, :infinity)
def flip do
GenServer.call(__MODULE__, :flip, @call_timeout_ms)
end
@doc "Register a diff to be stored until a flip."
def register_sync_cmd(remote_id, kind, body) do
GenServer.call(__MODULE__, {:register_sync_cmd, remote_id, kind, body}, :infinity)
GenServer.call(__MODULE__, {:register_sync_cmd, remote_id, kind, body})
end
@doc false
@ -56,6 +59,17 @@ defmodule Farmbot.Repo do
GenServer.start_link(__MODULE__, repos, name: __MODULE__)
end
defmodule State do
@moduledoc false
defstruct [
:repos,
:needs_hard_sync,
:timer,
:sync_pid,
:from
]
end
def init([repo_a, repo_b]) do
# Delete any old sync cmds.
destroy_all_sync_cmds()
@ -88,61 +102,98 @@ defmodule Farmbot.Repo do
# Copy configs
[current, _] = repos
copy_configs(current)
{:ok, %{repos: repos, needs_hard_sync: needs_hard_sync, timer: start_timer()}}
:ok = copy_configs(current)
{:ok, %State{repos: repos, needs_hard_sync: needs_hard_sync, timer: start_timer(), sync_pid: nil}}
end
def terminate(reason, _) do
def terminate(reason, state) do
if reason not in [:normal, :shutdown] do
Logger.error 1, "Repo died: #{inspect reason}"
BotState.set_sync_status(:sync_error)
end
if state.from do
GenServer.reply(state.from, reason)
end
Farmbot.FarmEvent.Manager.register_events([])
end
def handle_info({:DOWN, _, :process, _, %State{} = new_state}, %State{} = state) do
Logger.success(1, "Sync complete.")
if state.from do
GenServer.reply(state.from, :ok)
end
{:noreply, new_state}
end
def handle_info({:DOWN, _, :process, _, reason}, state) do
Logger.error 1, "Sync error: #{inspect reason}"
if state.from do
GenServer.reply(state.from, reason)
end
BotState.set_sync_status(:sync_now)
destroy_all_sync_cmds()
{:noreply, %State{state | sync_pid: nil, from: nil}}
end
def handle_info(:timeout, state) do
BotState.set_sync_status(:sync_now)
destroy_all_sync_cmds()
{:noreply, %State{state | timer: start_timer(), needs_hard_sync: true}}
end
def handle_call(:force_hard_sync, _, state) do
maybe_cancel_timer(state.timer)
BotState.set_sync_status(:sync_now)
Farmbot.FarmEvent.Manager.register_events([])
{:reply, :ok, %{state | timer: nil, needs_hard_sync: true}}
{:reply, :ok, %State{state | timer: nil, needs_hard_sync: true}}
end
def handle_call(:current_repo, _, %{repos: [repo_a, _]} = state) do
def handle_call(:current_repo, _, %State{repos: [repo_a, _]} = state) do
{:reply, repo_a, state}
end
def handle_call(:other_repo, _, %{repos: [_, repo_b]} = state) do
def handle_call(:other_repo, _, %State{repos: [_, repo_b]} = state) do
{:reply, repo_b, state}
end
def handle_call(:flip, _, %{repos: [repo_a, repo_b], needs_hard_sync: true} = state) do
maybe_cancel_timer(state.timer)
destroy_all_sync_cmds()
def handle_call(:flip, from, %State{repos: [repo_a, repo_b], needs_hard_sync: true} = state) do
fun = fn() ->
maybe_cancel_timer(state.timer)
destroy_all_sync_cmds()
BotState.set_sync_status(:syncing)
do_sync_both(repo_a, repo_b)
BotState.set_sync_status(:synced)
:ok = copy_configs(repo_b)
flip_repos_in_cs()
exit(%State{state | repos: [repo_b, repo_a], needs_hard_sync: false, timer: start_timer(), sync_pid: nil})
end
Logger.busy(1, "Syncing.")
BotState.set_sync_status(:syncing)
do_sync_both(repo_a, repo_b)
BotState.set_sync_status(:synced)
copy_configs(repo_b)
flip_repos_in_cs()
Logger.success(1, "Sync complete.")
{:reply, :ok, %{state | repos: [repo_b, repo_a], needs_hard_sync: false, timer: start_timer()}}
pid = spawn(fun)
Process.monitor(pid)
{:noreply, %State{state | sync_pid: pid, from: from}}
end
def handle_call(:flip, _, %{repos: [repo_a, repo_b]} = state) do
maybe_cancel_timer(state.timer)
def handle_call(:flip, from, %State{repos: [repo_a, repo_b]} = state) do
fun = fn() ->
maybe_cancel_timer(state.timer)
BotState.set_sync_status(:syncing)
# Fetch all sync_cmds and apply them in order they were received.
ConfigStorage.all(SyncCmd)
|> Enum.sort(&Timex.before?(&1.inserted_at, &2.inserted_at))
|> Enum.each(&apply_sync_cmd(repo_a, &1))
flip_repos_in_cs()
BotState.set_sync_status(:synced)
:ok = copy_configs(repo_b)
destroy_all_sync_cmds()
exit(%State{state | repos: [repo_b, repo_a], timer: start_timer(), sync_pid: nil})
end
Logger.busy(1, "Syncing.")
BotState.set_sync_status(:syncing)
# Fetch all sync_cmds and apply them in order they were received.
ConfigStorage.all(SyncCmd)
|> Enum.sort(&Timex.before?(&1.inserted_at, &2.inserted_at))
|> Enum.each(&apply_sync_cmd(repo_a, &1))
flip_repos_in_cs()
BotState.set_sync_status(:synced)
copy_configs(repo_b)
destroy_all_sync_cmds()
Logger.success(1, "Sync complete.")
{:reply, repo_b, %{state | repos: [repo_b, repo_a], timer: start_timer()}}
pid = spawn(fun)
Process.monitor(pid)
{:noreply, %State{state | sync_pid: pid, from: from}}
end
def handle_call({:register_sync_cmd, remote_id, kind, body}, _from, state) do
@ -152,28 +203,21 @@ defmodule Farmbot.Repo do
case SyncCmd.changeset(struct(SyncCmd, %{remote_id: remote_id, kind: kind, body: body}))
|> ConfigStorage.insert() do
{:ok, sync_cmd} ->
apply_sync_cmd(other_repo, sync_cmd)
:ok = apply_sync_cmd(other_repo, sync_cmd)
case auto_sync?() do
false -> BotState.set_sync_status(:sync_now)
true -> BotState.set_sync_status(:syncing)
false -> :ok = BotState.set_sync_status(:sync_now)
true -> :ok = BotState.set_sync_status(:syncing)
end
{:reply, :ok, %{state | timer: start_timer()}}
{:reply, :ok, %State{state | timer: start_timer()}}
{:error, reason} ->
BotState.set_sync_status(:sync_error)
Logger.error(1, "Failed to apply sync command: #{inspect(reason)}")
{:reply, :error, %{state | needs_hard_sync: true}}
{:reply, :error, %State{state | needs_hard_sync: true}}
end
end
def handle_info(:timeout, state) do
BotState.set_sync_status(:sync_now)
destroy_all_sync_cmds()
{:noreply, %{state | timer: start_timer(), needs_hard_sync: true}}
end
defp copy_configs(repo) do
case repo.one(Device) do
nil ->
@ -187,11 +231,12 @@ defmodule Farmbot.Repo do
repo.all(Peripheral)
|> Enum.all?(fn %{mode: mode, pin: pin} ->
mode = if mode == 0, do: :digital, else: :analog
# Logger.busy 3, "Reading peripheral (#{pin} - #{mode})"
# Logger.busy 3, "Reading peripheral (#{pin} - #{mode})"
Farmbot.Firmware.read_pin(pin, mode)
end)
Farmbot.FarmEvent.Manager.register_events repo.all(Farmbot.Repo.FarmEvent)
:ok
end
defp destroy_all_sync_cmds do
@ -282,12 +327,13 @@ defmodule Farmbot.Repo do
nil ->
mod.changeset(struct(mod), not_struct)
|> repo.insert!
:ok
# if there is an existing record, copy the ecto meta from the old
# record. This allows `insert_or_update` to work properly.
existing ->
mod.changeset(existing, not_struct)
|> repo.update!
:ok
end
else
Logger.warn(3, "Unknown module: #{mod} #{inspect(sync_cmd)}")

View File

@ -164,7 +164,7 @@ defmodule Farmbot.System.Updates.SlackUpdater do
{'Authorization', 'Bearer #{state.token}'}
]) do
{:ok, path} ->
Farmbot.System.Updates.apply_firmware(path)
Farmbot.System.Updates.apply_firmware(path, true)
{:stop, :normal, %{state | updating: true}}
{:error, reason} ->

View File

@ -19,13 +19,18 @@ defmodule Farmbot.System.UpdateTimer do
GenServer.start_link(__MODULE__, [], [name: __MODULE__])
end
def terminate(reason, _) do
Logger.error 1, "Failed to check updates: #{inspect reason}"
end
def init([]) do
spawn __MODULE__, :wait_for_http, [self()]
{:ok, [], :hibernate}
end
def handle_info(:checkup, state) do
Farmbot.System.Updates.check_updates()
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}
end

View File

@ -5,6 +5,7 @@ defmodule Farmbot.System.Updates do
@data_path Application.get_env(:farmbot, :data_path)
@current_version Farmbot.Project.version()
@target Farmbot.Project.target()
@current_commit Farmbot.Project.commit()
@env Farmbot.Project.env()
use Farmbot.Logger
@ -13,13 +14,20 @@ defmodule Farmbot.System.Updates do
alias Farmbot.System.ConfigStorage
@doc "Overwrite os update server field"
def override_update_server(url) do
ConfigStorage.update_config_value(:bool, "settings", "beta_opt_in", true)
ConfigStorage.update_config_value(:string, "settings", "os_update_server_overwrite", url)
end
@doc "Force check updates."
def check_updates do
def check_updates(reboot) do
token = ConfigStorage.get_config_value(:string, "authorization", "token")
if token do
case Farmbot.Jwt.decode(token) do
{:ok, %{os_update_server: update_server}} ->
do_check_updates_http(update_server)
{:ok, %Farmbot.Jwt{os_update_server: update_server}} ->
override = ConfigStorage.get_config_value(:string, "settings", "os_update_server_overwrite")
do_check_updates_http(override || update_server, reboot)
_ -> no_token()
end
else
@ -32,28 +40,43 @@ defmodule Farmbot.System.Updates do
:ok
end
defp do_check_updates_http(url) do
defp do_check_updates_http(url, reboot) do
Logger.info 3, "Checking: #{url} for updates."
with {:ok, %{body: body, status_code: 200}} <- Farmbot.HTTP.get(url),
{:ok, data} <- Poison.decode(body),
{:ok, prerelease} <- Map.fetch(data, "prerelease"),
{:ok, new_commit} <- Map.fetch(data, "target_commitish"),
{:ok, cl} <- Map.fetch(data, "body"),
{:ok, false} <- Map.fetch(data, "draft"),
{:ok, "v" <> new_version_str} <- Map.fetch(data, "tag_name"),
{:ok, new_version} <- Version.parse(new_version_str),
{:ok, current_version} <- Version.parse(@current_version),
{:ok, fw_url} <- find_fw_url(data, new_version) do
needs_update = case Version.compare(current_version, new_version) do
s when s in [:gt, :eq] ->
Logger.success 2, "Farmbot is up to date."
false
:lt ->
Logger.busy 1, "New Farmbot firmware update: #{new_version}"
true
needs_update = if prerelease do
val = new_commit == @current_commit
Logger.info 1, "Checking prerelease commits: current_commit: #{@current_commit} new_commit: #{new_commit} #{val}"
!val
else
case Version.compare(current_version, new_version) do
s when s in [:gt, :eq] ->
Logger.success 2, "Farmbot is up to date."
false
:lt ->
Logger.busy 1, "New Farmbot firmware update: #{new_version}"
true
end
end
if should_apply_update(@env, prerelease, needs_update) do
Logger.info 1, "Downloading update. Here is the release notes"
Logger.info 1, cl
do_download_and_apply(fw_url, new_version)
Logger.busy 1, "Downloading FarmbotOS over the air update"
IO.puts cl
# Logger.info 1, "Downloading update. Here is the release notes"
# Logger.info 1, cl
do_download_and_apply(fw_url, new_version, reboot)
else
:no_update
end
else
:error ->
@ -71,7 +94,7 @@ defmodule Farmbot.System.Updates do
{:ok, res} -> res
_ -> body
end
Logger.error 1, "HTTP error: #{code}: #{reason}"
Logger.error 1, "OS Update HTTP error: #{code}: #{inspect reason}"
end
end
@ -94,30 +117,38 @@ defmodule Farmbot.System.Updates do
defp should_apply_update(env, prerelease?, needs_update?)
defp should_apply_update(_, _, false), do: false
defp should_apply_update(:prod, true, _) do
Logger.info 3, "Not applying prerelease firmware."
false
if ConfigStorage.get_config_value(:bool, "settings", "beta_opt_in") do
Logger.info 3, "Applying beta update for production firmware"
true
else
Logger.info 3, "Not applying prerelease update for production firmware"
false
end
end
defp should_apply_update(_env, true, _) do
Logger.info 3, "Applying prerelease firmware."
true
end
defp should_apply_update(_, _, true) do
true
end
defp do_download_and_apply(dl_url, new_version) do
defp do_download_and_apply(dl_url, new_version, reboot) do
dl_fun = Farmbot.BotState.download_progress_fun("FBOS_OTA")
dl_path = Path.join(@data_path, "#{new_version}.fw")
case Farmbot.HTTP.download_file(dl_url, dl_path, dl_fun, "", []) do
{:ok, path} ->
apply_firmware(path)
apply_firmware(path, reboot)
{:error, reason} ->
Logger.error 1, "Failed to download update file: #{inspect reason}"
{:error, reason}
end
end
@doc "Apply an OS (fwup) firmware."
def apply_firmware(file_path, reboot \\ false) do
def apply_firmware(file_path, reboot) do
Logger.busy 1, "Applying #{@target} OS update"
before_update()
case @handler.apply_firmware(file_path) do
@ -129,6 +160,7 @@ defmodule Farmbot.System.Updates do
end
{:error, reason} ->
Logger.error 1, "Failed to apply update: #{inspect reason}"
{:error, reason}
end
end

View File

@ -37,6 +37,48 @@ defmodule Mix.Tasks.Farmbot.Env do
to_string(Farmbot.Project.env())
end
@doc false
def format_date_time(%{ctime: {{yr, m, day}, {hr, min, sec}}}) do
dt =
%DateTime{
hour: hr,
year: yr,
month: m,
day: day,
minute: min,
second: sec,
time_zone: "Etc/UTC",
zone_abbr: "UTC",
std_offset: 0,
utc_offset: 0
}
|> Timex.local()
"#{dt.year}-#{pad(dt.month)}-#{pad(dt.day)}_#{pad(dt.hour)}#{pad(dt.minute)}"
end
defp pad(int) do
if int < 10, do: "0#{int}", else: "#{int}"
end
@doc false
def build_comment(time, comment) do
"""
*New Farmbot Firmware!*
> *_Env_*: `#{env()}`
> *_Target_*: `#{target()}`
> *_Version_*: `#{mix_config(:version)}`
> *_Commit_*: `#{mix_config(:commit)}`
> *_Time_*: `#{time}`
#{commit_message()}
#{String.trim(comment)}
"""
end
defp commit_message do
System.cmd("git", ~w(log -1 --pretty=%B)) |> elem(0) |> String.trim()
end
@doc false
def slack_token do
System.get_env("SLACK_TOKEN") || Mix.raise "No $SLACK_TOKEN environment variable."

View File

@ -0,0 +1,25 @@
defmodule Mix.Tasks.Farmbot.ProdImage do
@moduledoc "Build a production firmware image file"
@shortdoc @moduledoc
use Mix.Task
import Mix.Tasks.Farmbot.Env
def run(opts) do
{keywords, _, _} =
opts |> OptionParser.parse(switches: [signed: :boolean])
signed? = Keyword.get(keywords, :signed, false)
Application.ensure_all_started(:timex)
fw_file_to_upload = if signed?, do: signed_fw_file(), else: fw_file()
time = format_date_time(File.stat!(fw_file_to_upload))
filename =
"#{mix_config(:app)}-#{target()}-#{env()}-#{mix_config(:commit)}#{
if signed?, do: "-signed-", else: "-"
}#{time}.img"
Mix.shell().info(build_comment(time, ""))
Mix.Tasks.Firmware.Image.run([filename])
end
end

View File

@ -54,51 +54,7 @@ defmodule Mix.Tasks.Farmbot.Firmware.Slack do
end
end
defp build_comment(time, comment) do
# %{
# env: env(),
# target: target(),
# version: mix_config(:version),
# commit: mix_config(:commit),
# time: time,
# comment: comment
# } |> Poison.encode!(pretty: true)
"""
*New Farmbot Firmware!*
> *_Env_*: `#{env()}`
> *_Target_*: `#{target()}`
> *_Version_*: `#{mix_config(:version)}`
> *_Commit_*: `#{mix_config(:commit)}`
> *_Time_*: `#{time}`
#{String.trim(comment)}
"""
end
defp error(msg) do
Mix.raise("Upload failed! " <> msg)
end
defp format_date_time(%{ctime: {{yr, m, day}, {hr, min, sec}}}) do
dt =
%DateTime{
hour: hr,
year: yr,
month: m,
day: day,
minute: min,
second: sec,
time_zone: "Etc/UTC",
zone_abbr: "UTC",
std_offset: 0,
utc_offset: 0
}
|> Timex.local()
"#{dt.year}-#{pad(dt.month)}-#{pad(dt.day)}_#{pad(dt.hour)}#{pad(dt.minute)}"
end
defp pad(int) do
if int < 10, do: "0#{int}", else: "#{int}"
end
end

13
mix.exs
View File

@ -4,8 +4,7 @@ defmodule Farmbot.Mixfile do
@version Path.join(__DIR__, "VERSION") |> File.read!() |> String.trim()
defp commit() do
{t, _} = System.cmd("git", ["log", "--pretty=format:%h", "-1"])
t
System.cmd("git", ~w"rev-parse --verify HEAD") |> elem(0) |> String.trim()
end
Mix.shell().info([
@ -23,7 +22,7 @@ defmodule Farmbot.Mixfile do
app: :farmbot,
description: "The Brains of the Farmbot Project",
package: package(),
compilers: [:elixir_make] ++ Mix.compilers,
compilers: compilers(),
make_clean: ["clean"],
test_coverage: [tool: ExCoveralls],
version: @version,
@ -75,6 +74,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
end
end
defp deps do
[
{:nerves, "~> 0.8.3", runtime: false},

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
defmodule Farmbot.System.ConfigStorage.Migrations.AddBetaChanelConfig do
use Ecto.Migration
import Farmbot.System.ConfigStorage.MigrationHelpers
def change do
create_settings_config("beta_opt_in", :bool, false)
create_settings_config("os_update_server_overwrite", :string, nil)
end
end

View File

@ -0,0 +1,9 @@
defmodule Farmbot.System.ConfigStorage.Migrations.AddSpecialFwMigrationConfig do
use Ecto.Migration
import Farmbot.System.ConfigStorage.MigrationHelpers
def change do
create_settings_config("fw_upgrade_migration", :bool, true)
end
end

File diff suppressed because it is too large Load Diff