Merge pull request #1197 from FarmBot/staging
Production release of v9.2.2qa/why_are_otas_broke v9.2.2
commit
a57f44976b
|
@ -201,6 +201,7 @@ jobs:
|
||||||
mix compile
|
mix compile
|
||||||
mix format --check-formatted
|
mix format --check-formatted
|
||||||
mix coveralls.json
|
mix coveralls.json
|
||||||
|
bash <(curl -s https://codecov.io/bash)
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: v14-fbcs-test-dependency-cache-{{ checksum "farmbot_celery_script/mix.lock" }}
|
key: v14-fbcs-test-dependency-cache-{{ checksum "farmbot_celery_script/mix.lock" }}
|
||||||
paths:
|
paths:
|
||||||
|
@ -235,6 +236,7 @@ jobs:
|
||||||
mix compile
|
mix compile
|
||||||
mix format --check-formatted
|
mix format --check-formatted
|
||||||
mix coveralls.json
|
mix coveralls.json
|
||||||
|
bash <(curl -s https://codecov.io/bash)
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: v14-fbfw-test-dependency-cache-{{ checksum "farmbot_firmware/mix.lock" }}
|
key: v14-fbfw-test-dependency-cache-{{ checksum "farmbot_firmware/mix.lock" }}
|
||||||
paths:
|
paths:
|
||||||
|
@ -279,6 +281,7 @@ jobs:
|
||||||
mix compile
|
mix compile
|
||||||
mix format --check-formatted
|
mix format --check-formatted
|
||||||
mix coveralls.json --trace
|
mix coveralls.json --trace
|
||||||
|
bash <(curl -s https://codecov.io/bash)
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: v14-fbcore-test-dependency-cache-{{ checksum "farmbot_core/mix.lock" }}
|
key: v14-fbcore-test-dependency-cache-{{ checksum "farmbot_core/mix.lock" }}
|
||||||
paths:
|
paths:
|
||||||
|
@ -327,6 +330,7 @@ jobs:
|
||||||
mix ecto.create
|
mix ecto.create
|
||||||
mix ecto.migrate
|
mix ecto.migrate
|
||||||
mix coveralls.json
|
mix coveralls.json
|
||||||
|
bash <(curl -s https://codecov.io/bash)
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: v14-fbext-test-dependency-cache-{{ checksum "farmbot_ext/mix.lock" }}
|
key: v14-fbext-test-dependency-cache-{{ checksum "farmbot_ext/mix.lock" }}
|
||||||
paths:
|
paths:
|
||||||
|
@ -362,6 +366,7 @@ jobs:
|
||||||
mix compile
|
mix compile
|
||||||
mix format --check-formatted
|
mix format --check-formatted
|
||||||
mix coveralls.json
|
mix coveralls.json
|
||||||
|
bash <(curl -s https://codecov.io/bash)
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: v14-fbos-host-test-dependency-cache-{{ checksum "farmbot_os/mix.lock" }}
|
key: v14-fbos-host-test-dependency-cache-{{ checksum "farmbot_os/mix.lock" }}
|
||||||
paths:
|
paths:
|
||||||
|
@ -374,58 +379,6 @@ jobs:
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: farmbot_os/cover
|
path: farmbot_os/cover
|
||||||
|
|
||||||
report_coverage:
|
|
||||||
<<: *defaults
|
|
||||||
environment:
|
|
||||||
MIX_ENV: test
|
|
||||||
MIX_TARGET: host
|
|
||||||
NERVES_LOG_DISABLE_PROGRESS_BAR: "yes"
|
|
||||||
ELIXIR_VERSION: 1.8.0
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- run: git submodule update --init --recursive
|
|
||||||
- <<: *install_elixir
|
|
||||||
- <<: *install_hex_archives
|
|
||||||
- <<: *install_mdl
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- v14-fbsupport-test-dependency-cache-{{ checksum "mix.lock" }}
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- v14-fbcs-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- v14-fbcs-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- v14-fbfw-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- v14-fbcore-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- v14-fbext-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- v14-fbos-coverage-cache-{{ .Branch }}-{{ .Revision }}
|
|
||||||
- run:
|
|
||||||
name: Check documentation formatting
|
|
||||||
command: |
|
|
||||||
mdl docs/
|
|
||||||
- run:
|
|
||||||
name: Report Coverage
|
|
||||||
working_directory: /nerves/build/
|
|
||||||
command: |
|
|
||||||
mix deps.get
|
|
||||||
mix compile
|
|
||||||
mix format --check-formatted
|
|
||||||
mix farmbot.coveralls circle
|
|
||||||
- save_cache:
|
|
||||||
key: v14-fbsupport-test-dependency-cache-{{ checksum "mix.lock" }}
|
|
||||||
paths:
|
|
||||||
- deps/
|
|
||||||
- _build
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# target=rpi app_env=prod #
|
# target=rpi app_env=prod #
|
||||||
################################################################################
|
################################################################################
|
||||||
|
@ -719,22 +672,6 @@ workflows:
|
||||||
- beta
|
- beta
|
||||||
- next
|
- next
|
||||||
- /^qa\/.*/
|
- /^qa\/.*/
|
||||||
- report_coverage:
|
|
||||||
context: org-global
|
|
||||||
requires:
|
|
||||||
- test_farmbot_celery_script
|
|
||||||
- test_farmbot_firmware
|
|
||||||
- test_farmbot_core
|
|
||||||
- test_farmbot_ext
|
|
||||||
- test_farmbot_os
|
|
||||||
filters:
|
|
||||||
branches:
|
|
||||||
ignore:
|
|
||||||
- master
|
|
||||||
- staging
|
|
||||||
- beta
|
|
||||||
- next
|
|
||||||
- /^qa\/.*/
|
|
||||||
|
|
||||||
# master branch to staging.farmbot.io
|
# master branch to staging.farmbot.io
|
||||||
nerves_hub_prod_stable_staging:
|
nerves_hub_prod_stable_staging:
|
||||||
|
|
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -1,5 +1,17 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
# 9.2.2
|
||||||
|
|
||||||
|
* Fix firmware locking error ("Can't perform X in Y state")
|
||||||
|
* Removal of dead code / legacy plus numerous unit test additions.
|
||||||
|
* Added coveralls test coverage reporter
|
||||||
|
* Unit test additions (+2.7% coverage :tada:)
|
||||||
|
* Updates to build instructions for third party developers
|
||||||
|
* Bug fix for criteria-based groups that have only one filter criteria.
|
||||||
|
* Bug fix for express bots involving timeout during remote firmware flash
|
||||||
|
* Remove VCR again (for now)
|
||||||
|
* Increase farmware timeout to 20 minutes (use at own risk)
|
||||||
|
|
||||||
# 9.2.1
|
# 9.2.1
|
||||||
|
|
||||||
* Improve firmware debug messages.
|
* Improve firmware debug messages.
|
||||||
|
|
|
@ -9,9 +9,9 @@ Old versions of FarmBot OS can be found [here](https://github.com/FarmBot/farmbo
|
||||||
---
|
---
|
||||||
|
|
||||||
## Build status
|
## Build status
|
||||||
| Master Build Status | Staging Build Status |
|
| Master Build Status | Staging Build Status | Test Coverage |
|
||||||
| :---: | :---: |
|
| :---: | :---: | :---: |
|
||||||
| [![Master Build Status](https://circleci.com/gh/FarmBot/farmbot_os/tree/master.svg?style=svg)](https://circleci.com/gh/FarmBot/farmbot_os/tree/master) | [![Staging Build Status](https://circleci.com/gh/FarmBot/farmbot_os/tree/staging.svg?style=svg)](https://circleci.com/gh/FarmBot/farmbot_os/tree/staging) |
|
| [![Master Build Status](https://circleci.com/gh/FarmBot/farmbot_os/tree/master.svg?style=svg)](https://circleci.com/gh/FarmBot/farmbot_os/tree/master) | [![Staging Build Status](https://circleci.com/gh/FarmBot/farmbot_os/tree/staging.svg?style=svg)](https://circleci.com/gh/FarmBot/farmbot_os/tree/staging) | [![codecov](https://codecov.io/gh/FarmBot/farmbot_os/branch/staging/graph/badge.svg)](https://codecov.io/gh/FarmBot/farmbot_os) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,30 @@ This document will act as an index to available documentation.
|
||||||
|
|
||||||
* [FarmBot Source Code common terms](/docs/glossary.md)
|
* [FarmBot Source Code common terms](/docs/glossary.md)
|
||||||
|
|
||||||
|
## Cheat Sheet
|
||||||
|
|
||||||
|
**Create a *.fw file from local repo (RPi Zero):**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
NERVES_SYSTEM=farmbot_system_rpi MIX_TARGET=rpi mix deps.get
|
||||||
|
NERVES_SYSTEM=farmbot_system_rpi MIX_TARGET=rpi mix firmware
|
||||||
|
sudo fwup farmbot_os/_build/rpi/rpi_dev/nerves/images/farmbot.fw
|
||||||
|
```
|
||||||
|
|
||||||
|
**Create a *.fw file from local repo (RPi v3):**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
NERVES_SYSTEM=farmbot_system_rpi3 MIX_TARGET=rpi3 mix deps.get
|
||||||
|
NERVES_SYSTEM=farmbot_system_rpi3 MIX_TARGET=rpi3 mix firmware
|
||||||
|
sudo fwup farmbot_os/_build/rpi3/rpi3_dev/nerves/images/farmbot.fw
|
||||||
|
```
|
||||||
|
|
||||||
|
**Create or Update the Nerves System:**
|
||||||
|
|
||||||
|
Please see the official [Nerves documentation on "Nerves Systems"](https://hexdocs.pm/nerves/0.4.0/systems.html).
|
||||||
|
|
||||||
|
HINT: You may want to [develop the system locally](https://stackoverflow.com/a/28189056/1064917)
|
||||||
|
|
||||||
## Hardware specifics
|
## Hardware specifics
|
||||||
|
|
||||||
Most FarmBot development/testing is done on a standard desktop PC.
|
Most FarmBot development/testing is done on a standard desktop PC.
|
||||||
|
|
|
@ -20,15 +20,22 @@ string.
|
||||||
```bash
|
```bash
|
||||||
cd $FARMBOT_OS_ROOT_DIRECTORY
|
cd $FARMBOT_OS_ROOT_DIRECTORY
|
||||||
git checkout staging
|
git checkout staging
|
||||||
git fetch --all && git reset --hard origin/staging
|
|
||||||
|
# Ensure you don't accidentally publish local changes
|
||||||
|
# that have not gone through CI:
|
||||||
|
git fetch --all
|
||||||
|
git reset --hard origin/staging
|
||||||
|
|
||||||
# update the CHANGELOG, but DO NOT put the `rc`
|
# update the CHANGELOG, but DO NOT put the `rc`
|
||||||
# on the semver string.
|
# on the semver string.
|
||||||
$EDITOR CHANGELOG.md
|
$EDITOR CHANGELOG.md
|
||||||
echo 10.5.6-rc30 > VERSION
|
|
||||||
git add CHANGELOG.md VERSION
|
echo 1.2.3-rc4 > VERSION
|
||||||
git commit -m "Release v10.5.6-rc30"
|
|
||||||
git tag v$(cat VERSION)
|
git add -A
|
||||||
git push origin staging v$(cat VERSION)
|
git commit -am "Release v10.5.6-rc30"
|
||||||
|
git tag v1.2.3-rc4
|
||||||
|
git push origin v1.2.3-rc4
|
||||||
```
|
```
|
||||||
|
|
||||||
or call the helper script:
|
or call the helper script:
|
||||||
|
|
|
@ -25,3 +25,4 @@ farmbot_ng-*.tar
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
*.so
|
*.so
|
||||||
*.hex
|
*.hex
|
||||||
|
*.coverdata
|
|
@ -5,10 +5,35 @@ defmodule FarmbotCeleryScript.AST.Factory do
|
||||||
|
|
||||||
alias FarmbotCeleryScript.AST
|
alias FarmbotCeleryScript.AST
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Create an empty AST WITH ARG SET TO `nil`.
|
||||||
|
|
||||||
|
iex> new()
|
||||||
|
%FarmbotCeleryScript.AST{
|
||||||
|
args: nil,
|
||||||
|
body: [],
|
||||||
|
comment: nil,
|
||||||
|
kind: nil,
|
||||||
|
meta: nil
|
||||||
|
}
|
||||||
|
"""
|
||||||
def new do
|
def new do
|
||||||
%AST{body: []}
|
%AST{body: []}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Create a new AST to work with. Strings `kind`s are
|
||||||
|
converted to symbols.
|
||||||
|
|
||||||
|
iex> new("foo")
|
||||||
|
%FarmbotCeleryScript.AST{
|
||||||
|
args: %{},
|
||||||
|
body: [],
|
||||||
|
comment: nil,
|
||||||
|
kind: :foo,
|
||||||
|
meta: nil
|
||||||
|
}
|
||||||
|
"""
|
||||||
def new(kind, args \\ %{}, body \\ []) do
|
def new(kind, args \\ %{}, body \\ []) do
|
||||||
AST.new(kind, Map.new(args), body)
|
AST.new(kind, Map.new(args), body)
|
||||||
end
|
end
|
||||||
|
@ -24,59 +49,148 @@ defmodule FarmbotCeleryScript.AST.Factory do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
iex> (new() |> rpc_request("x") |> set_pin_io_mode(13, 1)).body
|
||||||
|
[%FarmbotCeleryScript.AST{
|
||||||
|
kind: :set_pin_io_mode,
|
||||||
|
args: %{ pin_io_mode: 1, pin_number: 13 },
|
||||||
|
body: [],
|
||||||
|
comment: nil,
|
||||||
|
meta: nil
|
||||||
|
}]
|
||||||
|
"""
|
||||||
def set_pin_io_mode(%AST{} = ast, pin_number, pin_io_mode) do
|
def set_pin_io_mode(%AST{} = ast, pin_number, pin_io_mode) do
|
||||||
ast
|
args = %{pin_number: pin_number, pin_io_mode: pin_io_mode}
|
||||||
|> add_body_node(
|
ast |> add_body_node(new(:set_pin_io_mode, args))
|
||||||
new(:set_pin_io_mode, %{pin_number: pin_number, pin_io_mode: pin_io_mode})
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
iex> (new() |> rpc_request("x") |> emergency_lock()).body
|
||||||
|
[%FarmbotCeleryScript.AST{
|
||||||
|
body: [],
|
||||||
|
comment: nil,
|
||||||
|
meta: nil,
|
||||||
|
args: %{},
|
||||||
|
kind: :emergency_lock
|
||||||
|
}]
|
||||||
|
"""
|
||||||
def emergency_lock(%AST{} = ast) do
|
def emergency_lock(%AST{} = ast) do
|
||||||
ast
|
ast |> add_body_node(new(:emergency_lock))
|
||||||
|> add_body_node(new(:emergency_lock))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
iex> (new() |> rpc_request("x") |> emergency_unlock()).body
|
||||||
|
[%FarmbotCeleryScript.AST{
|
||||||
|
body: [],
|
||||||
|
comment: nil,
|
||||||
|
meta: nil,
|
||||||
|
args: %{},
|
||||||
|
kind: :emergency_unlock
|
||||||
|
}]
|
||||||
|
"""
|
||||||
def emergency_unlock(%AST{} = ast) do
|
def emergency_unlock(%AST{} = ast) do
|
||||||
ast
|
ast |> add_body_node(new(:emergency_unlock))
|
||||||
|> add_body_node(new(:emergency_unlock))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
iex> (new() |> rpc_request("x") |> read_status()).body
|
||||||
|
[%FarmbotCeleryScript.AST{
|
||||||
|
body: [],
|
||||||
|
comment: nil,
|
||||||
|
meta: nil,
|
||||||
|
args: %{},
|
||||||
|
kind: :read_status
|
||||||
|
}]
|
||||||
|
"""
|
||||||
def read_status(%AST{} = ast) do
|
def read_status(%AST{} = ast) do
|
||||||
ast
|
ast |> add_body_node(new(:read_status))
|
||||||
|> add_body_node(new(:read_status))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
iex> (new() |> rpc_request("x") |> power_off()).body
|
||||||
|
[%FarmbotCeleryScript.AST{
|
||||||
|
body: [],
|
||||||
|
comment: nil,
|
||||||
|
meta: nil,
|
||||||
|
args: %{},
|
||||||
|
kind: :power_off
|
||||||
|
}]
|
||||||
|
"""
|
||||||
def power_off(%AST{} = ast) do
|
def power_off(%AST{} = ast) do
|
||||||
ast
|
ast |> add_body_node(new(:power_off))
|
||||||
|> add_body_node(new(:power_off))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
iex> (new() |> rpc_request("x") |> reboot()).body
|
||||||
|
[%FarmbotCeleryScript.AST{
|
||||||
|
body: [],
|
||||||
|
comment: nil,
|
||||||
|
meta: nil,
|
||||||
|
args: %{},
|
||||||
|
kind: :reboot
|
||||||
|
}]
|
||||||
|
"""
|
||||||
def reboot(%AST{} = ast) do
|
def reboot(%AST{} = ast) do
|
||||||
ast
|
ast |> add_body_node(new(:reboot))
|
||||||
|> add_body_node(new(:reboot))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
iex> (new() |> rpc_request("x") |> sync()).body
|
||||||
|
[%FarmbotCeleryScript.AST{
|
||||||
|
body: [],
|
||||||
|
comment: nil,
|
||||||
|
meta: nil,
|
||||||
|
args: %{},
|
||||||
|
kind: :sync
|
||||||
|
}]
|
||||||
|
"""
|
||||||
def sync(%AST{} = ast) do
|
def sync(%AST{} = ast) do
|
||||||
ast
|
ast |> add_body_node(new(:sync))
|
||||||
|> add_body_node(new(:sync))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
iex> (new() |> rpc_request("x") |> take_photo()).body
|
||||||
|
[%FarmbotCeleryScript.AST{
|
||||||
|
body: [],
|
||||||
|
comment: nil,
|
||||||
|
meta: nil,
|
||||||
|
args: %{},
|
||||||
|
kind: :take_photo
|
||||||
|
}]
|
||||||
|
"""
|
||||||
def take_photo(%AST{} = ast) do
|
def take_photo(%AST{} = ast) do
|
||||||
ast
|
ast |> add_body_node(new(:take_photo))
|
||||||
|> add_body_node(new(:take_photo))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
iex> (new() |> rpc_request("x") |> flash_firmware("arduino")).body
|
||||||
|
[%FarmbotCeleryScript.AST{
|
||||||
|
kind: :flash_firmware,
|
||||||
|
comment: nil,
|
||||||
|
meta: nil,
|
||||||
|
args: %{package: "arduino"},
|
||||||
|
body: [],
|
||||||
|
}]
|
||||||
|
"""
|
||||||
def flash_firmware(%AST{} = ast, package) when is_binary(package) do
|
def flash_firmware(%AST{} = ast, package) when is_binary(package) do
|
||||||
ast
|
ast |> add_body_node(new(:flash_firmware, %{package: package}))
|
||||||
|> add_body_node(new(:flash_firmware, %{package: package}))
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
iex> (new() |> rpc_request("x") |> factory_reset("arduino")).body
|
||||||
|
[%FarmbotCeleryScript.AST{
|
||||||
|
kind: :factory_reset,
|
||||||
|
comment: nil,
|
||||||
|
meta: nil,
|
||||||
|
args: %{package: "arduino"},
|
||||||
|
body: [],
|
||||||
|
}]
|
||||||
|
"""
|
||||||
|
def factory_reset(%AST{} = ast, package) do
|
||||||
|
ast |> add_body_node(new(:factory_reset, %{package: package}))
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_body_node(%AST{body: body} = ast, %AST{} = body_node) do
|
def add_body_node(%AST{body: body} = ast, %AST{} = body_node) do
|
||||||
%{ast | body: body ++ [body_node]}
|
%{ast | body: body ++ [body_node]}
|
||||||
end
|
end
|
||||||
|
|
||||||
def factory_reset(%AST{} = ast, package) do
|
|
||||||
ast
|
|
||||||
|> add_body_node(new(:factory_reset, %{package: package}))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
defmodule FarmbotCeleryScript.AST.FactoryTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
doctest FarmbotCeleryScript.AST.Factory, import: true
|
||||||
|
end
|
|
@ -3,6 +3,7 @@ defmodule FarmbotCeleryScript.SchedulerTest do
|
||||||
use Mimic
|
use Mimic
|
||||||
alias FarmbotCeleryScript.{Scheduler, AST}
|
alias FarmbotCeleryScript.{Scheduler, AST}
|
||||||
alias FarmbotCeleryScript.SysCalls.Stubs
|
alias FarmbotCeleryScript.SysCalls.Stubs
|
||||||
|
import ExUnit.CaptureLog
|
||||||
|
|
||||||
setup :set_mimic_global
|
setup :set_mimic_global
|
||||||
setup :verify_on_exit!
|
setup :verify_on_exit!
|
||||||
|
@ -21,11 +22,14 @@ defmodule FarmbotCeleryScript.SchedulerTest do
|
||||||
|> AST.Factory.read_pin(9, 0)
|
|> AST.Factory.read_pin(9, 0)
|
||||||
|
|
||||||
scheduled_time = DateTime.utc_now() |> DateTime.add(100, :millisecond)
|
scheduled_time = DateTime.utc_now() |> DateTime.add(100, :millisecond)
|
||||||
|
# msg = "[info] Next execution is ready for execution: now"
|
||||||
{:ok, _} = Scheduler.schedule(sch, ast, scheduled_time, %{})
|
{:ok, _} = Scheduler.schedule(sch, ast, scheduled_time, %{})
|
||||||
|
|
||||||
# Hack to force the scheduler to checkup instead of waiting the normal 15 seconds
|
# Hack to force the scheduler to checkup instead of waiting the normal 15 seconds
|
||||||
send(sch, :checkup)
|
assert capture_log(fn ->
|
||||||
# Sorry.
|
send(sch, :checkup)
|
||||||
Process.sleep(1100)
|
# Sorry.
|
||||||
|
Process.sleep(1100)
|
||||||
|
end) =~ "[info] Next execution is ready for execution: now"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,9 @@ defmodule FarmbotCeleryScriptTest do
|
||||||
alias FarmbotCeleryScript.AST
|
alias FarmbotCeleryScript.AST
|
||||||
alias FarmbotCeleryScript.SysCalls.Stubs
|
alias FarmbotCeleryScript.SysCalls.Stubs
|
||||||
|
|
||||||
|
import ExUnit.CaptureIO
|
||||||
|
import ExUnit.CaptureLog
|
||||||
|
|
||||||
setup :verify_on_exit!
|
setup :verify_on_exit!
|
||||||
|
|
||||||
test "uses default values when no parameter is found" do
|
test "uses default values when no parameter is found" do
|
||||||
|
@ -59,8 +62,10 @@ defmodule FarmbotCeleryScriptTest do
|
||||||
:ok
|
:ok
|
||||||
end)
|
end)
|
||||||
|
|
||||||
result = FarmbotCeleryScript.execute(sequence_ast, me)
|
capture_log(fn ->
|
||||||
assert :ok == result
|
result = FarmbotCeleryScript.execute(sequence_ast, me)
|
||||||
|
assert :ok == result
|
||||||
|
end) =~ "[error] CeleryScript syscall stubbed: log"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "syscall errors" do
|
test "syscall errors" do
|
||||||
|
@ -93,11 +98,17 @@ defmodule FarmbotCeleryScriptTest do
|
||||||
}
|
}
|
||||||
|> AST.decode()
|
|> AST.decode()
|
||||||
|
|
||||||
expect(Stubs, :read_pin, fn _, _ -> raise("big oops") end)
|
expect(Stubs, :read_pin, fn _, _ ->
|
||||||
|
raise("big oops")
|
||||||
|
end)
|
||||||
|
|
||||||
assert {:error, "big oops"} ==
|
io =
|
||||||
FarmbotCeleryScript.execute(execute_ast, execute_ast)
|
capture_io(:stderr, fn ->
|
||||||
|
assert {:error, "big oops"} ==
|
||||||
|
FarmbotCeleryScript.execute(execute_ast, execute_ast)
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert io =~ "CeleryScript Exception"
|
||||||
assert_receive {:step_complete, ^execute_ast, {:error, "big oops"}}
|
assert_receive {:step_complete, ^execute_ast, {:error, "big oops"}}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use Mix.Config
|
use Mix.Config
|
||||||
|
config :logger, level: :warn
|
||||||
|
|
||||||
# must be lower than other timers
|
# must be lower than other timers
|
||||||
# To ensure other timers have time to timeout
|
# To ensure other timers have time to timeout
|
||||||
|
@ -17,3 +18,19 @@ config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 0
|
||||||
|
|
||||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
||||||
firmware_flash_attempt_threshold: 0
|
firmware_flash_attempt_threshold: 0
|
||||||
|
|
||||||
|
if Mix.env() == :test do
|
||||||
|
config :ex_unit, capture_logs: true
|
||||||
|
mapper = fn mod -> config :farmbot_core, mod, children: [] end
|
||||||
|
|
||||||
|
list = [
|
||||||
|
FarmbotCore,
|
||||||
|
FarmbotCore.StorageSupervisor,
|
||||||
|
FarmbotCore.Asset.Supervisor,
|
||||||
|
FarmbotCore.BotState.Supervisor,
|
||||||
|
FarmbotCore.Config.Supervisor,
|
||||||
|
FarmbotCore.Logger.Supervisor
|
||||||
|
]
|
||||||
|
|
||||||
|
Enum.map(list, mapper)
|
||||||
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"coverage_options": {
|
"coverage_options": {
|
||||||
"treat_no_relevant_lines_as_covered": true,
|
"treat_no_relevant_lines_as_covered": true,
|
||||||
"minimum_coverage": 24
|
"minimum_coverage": 25
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -14,8 +14,11 @@ defmodule FarmbotCore do
|
||||||
def start(_, args), do: Supervisor.start_link(__MODULE__, args, name: __MODULE__)
|
def start(_, args), do: Supervisor.start_link(__MODULE__, args, name: __MODULE__)
|
||||||
|
|
||||||
def init([]) do
|
def init([]) do
|
||||||
|
Supervisor.init(children(), [strategy: :one_for_one])
|
||||||
|
end
|
||||||
|
|
||||||
children = [
|
def children do
|
||||||
|
default = [
|
||||||
FarmbotCore.Leds,
|
FarmbotCore.Leds,
|
||||||
FarmbotCore.EctoMigrator,
|
FarmbotCore.EctoMigrator,
|
||||||
FarmbotCore.BotState.Supervisor,
|
FarmbotCore.BotState.Supervisor,
|
||||||
|
@ -27,6 +30,7 @@ defmodule FarmbotCore do
|
||||||
{FarmbotFirmware, transport: FarmbotFirmware.StubTransport, side_effects: FarmbotCore.FirmwareSideEffects},
|
{FarmbotFirmware, transport: FarmbotFirmware.StubTransport, side_effects: FarmbotCore.FirmwareSideEffects},
|
||||||
FarmbotCeleryScript.Scheduler
|
FarmbotCeleryScript.Scheduler
|
||||||
]
|
]
|
||||||
Supervisor.init(children, [strategy: :one_for_one])
|
config = (Application.get_env(:farmbot_ext, __MODULE__) || [])
|
||||||
|
Keyword.get(config, :children, default)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -62,11 +62,6 @@ defmodule FarmbotCore.Asset do
|
||||||
|
|
||||||
## Begin FarmEvent
|
## Begin FarmEvent
|
||||||
|
|
||||||
@doc "Returns all FarmEvents"
|
|
||||||
def list_farm_events do
|
|
||||||
Repo.all(FarmEvent)
|
|
||||||
end
|
|
||||||
|
|
||||||
def new_farm_event!(params) do
|
def new_farm_event!(params) do
|
||||||
%FarmEvent{}
|
%FarmEvent{}
|
||||||
|> FarmEvent.changeset(params)
|
|> FarmEvent.changeset(params)
|
||||||
|
@ -328,8 +323,7 @@ defmodule FarmbotCore.Asset do
|
||||||
# the DB / API.
|
# the DB / API.
|
||||||
sorted = CriteriaRetriever.run(point_group)
|
sorted = CriteriaRetriever.run(point_group)
|
||||||
|> sort_points(sort_by || "xy_ascending")
|
|> sort_points(sort_by || "xy_ascending")
|
||||||
|> Enum.map(&Map.fetch!(&1, :id))
|
|> Enum.map(fn point -> point.id end)
|
||||||
|
|
||||||
%{ point_group | point_ids: sorted }
|
%{ point_group | point_ids: sorted }
|
||||||
other ->
|
other ->
|
||||||
# Swallow all other errors
|
# Swallow all other errors
|
||||||
|
@ -353,7 +347,7 @@ defmodule FarmbotCore.Asset do
|
||||||
|> Repo.update!()
|
|> Repo.update!()
|
||||||
|
|
||||||
regimen_instances = list_regimen_instances()
|
regimen_instances = list_regimen_instances()
|
||||||
farm_events = list_farm_events()
|
farm_events = Repo.all(FarmEvent)
|
||||||
|
|
||||||
# check for any matching asset using this point group.
|
# check for any matching asset using this point group.
|
||||||
# This is pretty recursive and probably isn't super great
|
# This is pretty recursive and probably isn't super great
|
||||||
|
|
|
@ -22,7 +22,7 @@ defmodule FarmbotCore.Asset.CriteriaRetriever do
|
||||||
# We will not query any string/numeric fields other than these.
|
# We will not query any string/numeric fields other than these.
|
||||||
# Updating the PointGroup / Point models may require an update
|
# Updating the PointGroup / Point models may require an update
|
||||||
# to these fields.
|
# to these fields.
|
||||||
@numberic_fields ["radius", "x", "y", "z"]
|
@numberic_fields ["radius", "x", "y", "z", "pullout_direction"]
|
||||||
@string_fields ["name", "openfarm_slug", "plant_stage", "pointer_type"]
|
@string_fields ["name", "openfarm_slug", "plant_stage", "pointer_type"]
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -49,7 +49,7 @@ defmodule FarmbotCore.Asset.CriteriaRetriever do
|
||||||
needs_meta_filter = Repo.all(dynamic_query)
|
needs_meta_filter = Repo.all(dynamic_query)
|
||||||
# There we go. We have all the matching %Point{}s
|
# There we go. We have all the matching %Point{}s
|
||||||
search_matches = search_meta_fields(pg, needs_meta_filter)
|
search_matches = search_meta_fields(pg, needs_meta_filter)
|
||||||
# ...but there are duplicates. We can remove them via uniq_by:
|
# ...but there are duplicates. We can remove them via uniq_by:
|
||||||
Enum.uniq_by((search_matches ++ always_ok), fn p -> p.id end)
|
Enum.uniq_by((search_matches ++ always_ok), fn p -> p.id end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -114,7 +114,8 @@ defmodule FarmbotCore.Asset.CriteriaRetriever do
|
||||||
def finalize({fragments, criteria}) do
|
def finalize({fragments, criteria}) do
|
||||||
x = Enum.join(fragments, " AND ")
|
x = Enum.join(fragments, " AND ")
|
||||||
sql = "SELECT id FROM points WHERE #{x}"
|
sql = "SELECT id FROM points WHERE #{x}"
|
||||||
{:ok, query} = Repo.query(sql, List.flatten(criteria))
|
query_params = List.flatten(criteria)
|
||||||
|
{:ok, query} = Repo.query(sql, query_params)
|
||||||
%Sqlite.DbConnection.Result{ rows: rows } = query
|
%Sqlite.DbConnection.Result{ rows: rows } = query
|
||||||
List.flatten(rows)
|
List.flatten(rows)
|
||||||
end
|
end
|
||||||
|
@ -136,11 +137,13 @@ defmodule FarmbotCore.Asset.CriteriaRetriever do
|
||||||
if days == 0 do
|
if days == 0 do
|
||||||
{ pg, accum }
|
{ pg, accum }
|
||||||
else
|
else
|
||||||
|
|
||||||
op = day_criteria["op"] || "<"
|
op = day_criteria["op"] || "<"
|
||||||
time = Timex.shift(Timex.now(), days: -1 * days)
|
time = Timex.shift(Timex.now(), days: -1 * days)
|
||||||
|
|
||||||
{ pg, accum ++ [{"created_at", op, time}] }
|
inverted_op = if op == ">" do "<" else ">" end
|
||||||
|
|
||||||
|
{ pg, accum ++ [{"created_at", inverted_op, time}] }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -159,14 +162,19 @@ defmodule FarmbotCore.Asset.CriteriaRetriever do
|
||||||
# NOT OK: Repo.query("SELECT foo WHERE bar IN $0", [[1, 2, 3]])
|
# NOT OK: Repo.query("SELECT foo WHERE bar IN $0", [[1, 2, 3]])
|
||||||
# OK: Repo.query("SELECT foo WHERE bar IN ($0, $1, $2)", [1, 2, 3])
|
# OK: Repo.query("SELECT foo WHERE bar IN ($0, $1, $2)", [1, 2, 3])
|
||||||
defp stage_3({sql, args}, {full_query, full_args, count0}) when is_list(args) do
|
defp stage_3({sql, args}, {full_query, full_args, count0}) when is_list(args) do
|
||||||
final = count0 + Enum.count(args) - 1
|
arg_count = Enum.count(args)
|
||||||
|
final = count0 + (arg_count - 1)
|
||||||
initial_state = {sql, count0}
|
initial_state = {sql, count0}
|
||||||
{next_sql, _} = Enum.reduce(args, initial_state, fn
|
{next_sql, _} =
|
||||||
(_, {sql, ^count0}) -> {sql <> " ($#{count0},", count0+1}
|
if arg_count == 1 do
|
||||||
(_, {sql, ^final}) -> {sql <> " $#{final})", final}
|
{sql <> " ($#{count0})", nil}
|
||||||
(_, {sql, count}) -> {sql <> " $#{count},", count+1}
|
else
|
||||||
end)
|
Enum.reduce(args, initial_state, fn
|
||||||
|
(_, {sql, ^count0}) -> {sql <> " ($#{count0},", count0+1}
|
||||||
|
(_, {sql, ^final}) -> {sql <> " $#{final})", final}
|
||||||
|
(_, {sql, count}) -> {sql <> " $#{count},", count+1}
|
||||||
|
end)
|
||||||
|
end
|
||||||
{full_query ++ [next_sql], full_args ++ [args], final + 1}
|
{full_query ++ [next_sql], full_args ++ [args], final + 1}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -13,20 +13,21 @@ defmodule FarmbotCore.Asset.Point do
|
||||||
foreign_key: :asset_local_id
|
foreign_key: :asset_local_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
field(:discarded_at, :utc_datetime)
|
||||||
|
field(:gantry_mounted, :boolean)
|
||||||
field(:meta, :map)
|
field(:meta, :map)
|
||||||
|
field(:monitor, :boolean, default: true)
|
||||||
field(:name, :string)
|
field(:name, :string)
|
||||||
field(:openfarm_slug, :string)
|
field(:openfarm_slug, :string)
|
||||||
field(:plant_stage, :string)
|
field(:plant_stage, :string)
|
||||||
field(:planted_at, :utc_datetime)
|
field(:planted_at, :utc_datetime)
|
||||||
field(:pointer_type, :string)
|
field(:pointer_type, :string)
|
||||||
|
field(:pullout_direction, :integer)
|
||||||
field(:radius, :float)
|
field(:radius, :float)
|
||||||
|
field(:tool_id, :integer)
|
||||||
field(:x, :float)
|
field(:x, :float)
|
||||||
field(:y, :float)
|
field(:y, :float)
|
||||||
field(:z, :float)
|
field(:z, :float)
|
||||||
field(:tool_id, :integer)
|
|
||||||
field(:discarded_at, :utc_datetime)
|
|
||||||
field(:gantry_mounted, :boolean)
|
|
||||||
field(:monitor, :boolean, default: true)
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -42,6 +43,8 @@ defmodule FarmbotCore.Asset.Point do
|
||||||
tool_id: point.tool_id,
|
tool_id: point.tool_id,
|
||||||
discarded_at: point.discarded_at,
|
discarded_at: point.discarded_at,
|
||||||
gantry_mounted: point.gantry_mounted,
|
gantry_mounted: point.gantry_mounted,
|
||||||
|
openfarm_slug: point.openfarm_slug,
|
||||||
|
pullout_direction: point.pullout_direction,
|
||||||
x: point.x,
|
x: point.x,
|
||||||
y: point.y,
|
y: point.y,
|
||||||
z: point.z
|
z: point.z
|
||||||
|
@ -51,22 +54,24 @@ defmodule FarmbotCore.Asset.Point do
|
||||||
def changeset(point, params \\ %{}) do
|
def changeset(point, params \\ %{}) do
|
||||||
point
|
point
|
||||||
|> cast(params, [
|
|> cast(params, [
|
||||||
|
:created_at,
|
||||||
|
:discarded_at,
|
||||||
|
:gantry_mounted,
|
||||||
:id,
|
:id,
|
||||||
:meta,
|
:meta,
|
||||||
|
:monitor,
|
||||||
:name,
|
:name,
|
||||||
:plant_stage,
|
:plant_stage,
|
||||||
:planted_at,
|
:planted_at,
|
||||||
:pointer_type,
|
:pointer_type,
|
||||||
|
:pullout_direction,
|
||||||
|
:openfarm_slug,
|
||||||
:radius,
|
:radius,
|
||||||
|
:tool_id,
|
||||||
|
:updated_at,
|
||||||
:x,
|
:x,
|
||||||
:y,
|
:y,
|
||||||
:z,
|
:z,
|
||||||
:tool_id,
|
|
||||||
:gantry_mounted,
|
|
||||||
:discarded_at,
|
|
||||||
:monitor,
|
|
||||||
:created_at,
|
|
||||||
:updated_at
|
|
||||||
])
|
])
|
||||||
|> validate_required([])
|
|> validate_required([])
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,11 @@ defmodule FarmbotCore.Asset.Supervisor do
|
||||||
end
|
end
|
||||||
|
|
||||||
def init([]) do
|
def init([]) do
|
||||||
children = [
|
Supervisor.init(children(), strategy: :one_for_one)
|
||||||
|
end
|
||||||
|
|
||||||
|
def children do
|
||||||
|
default = [
|
||||||
Repo,
|
Repo,
|
||||||
{AssetSupervisor, module: FbosConfig},
|
{AssetSupervisor, module: FbosConfig},
|
||||||
{AssetSupervisor, module: FirmwareConfig},
|
{AssetSupervisor, module: FirmwareConfig},
|
||||||
|
@ -38,7 +42,7 @@ defmodule FarmbotCore.Asset.Supervisor do
|
||||||
{AssetSupervisor, module: FarmwareEnv},
|
{AssetSupervisor, module: FarmwareEnv},
|
||||||
AssetMonitor,
|
AssetMonitor,
|
||||||
]
|
]
|
||||||
|
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||||
Supervisor.init(children, strategy: :one_for_one)
|
Keyword.get(config, :children, default)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -82,7 +82,6 @@ defmodule FarmbotCore.AssetMonitor do
|
||||||
sub_state = Map.drop(sub_state, deleted_ids)
|
sub_state = Map.drop(sub_state, deleted_ids)
|
||||||
|
|
||||||
Enum.each(deleted_ids, fn local_id ->
|
Enum.each(deleted_ids, fn local_id ->
|
||||||
Logger.error("#{inspect(kind)} #{local_id} needs to be terminated")
|
|
||||||
AssetSupervisor.terminate_child(kind, local_id)
|
AssetSupervisor.terminate_child(kind, local_id)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -99,7 +98,6 @@ defmodule FarmbotCore.AssetMonitor do
|
||||||
Map.put(sub_state, id, updated_at)
|
Map.put(sub_state, id, updated_at)
|
||||||
|
|
||||||
compare_datetimes(updated_at, sub_state[id]) == :gt ->
|
compare_datetimes(updated_at, sub_state[id]) == :gt ->
|
||||||
Logger.warn("#{inspect(kind)} #{id} needs to be updated")
|
|
||||||
asset = Repo.preload(asset, AssetWorker.preload(asset))
|
asset = Repo.preload(asset, AssetWorker.preload(asset))
|
||||||
:ok = AssetSupervisor.update_child(asset) |> assert_result!(asset)
|
:ok = AssetSupervisor.update_child(asset) |> assert_result!(asset)
|
||||||
Map.put(sub_state, id, updated_at)
|
Map.put(sub_state, id, updated_at)
|
||||||
|
|
|
@ -25,8 +25,6 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.RegimenInstance do
|
||||||
|
|
||||||
@impl GenServer
|
@impl GenServer
|
||||||
def init([regimen_instance, _args]) do
|
def init([regimen_instance, _args]) do
|
||||||
Logger.warn "RegimenInstance #{inspect(regimen_instance)} initializing"
|
|
||||||
|
|
||||||
with %Regimen{} <- regimen_instance.regimen,
|
with %Regimen{} <- regimen_instance.regimen,
|
||||||
%FarmEvent{} <- regimen_instance.farm_event do
|
%FarmEvent{} <- regimen_instance.farm_event do
|
||||||
send self(), :schedule
|
send self(), :schedule
|
||||||
|
@ -40,25 +38,25 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.RegimenInstance do
|
||||||
def handle_info(:schedule, state) do
|
def handle_info(:schedule, state) do
|
||||||
regimen_instance = state.regimen_instance
|
regimen_instance = state.regimen_instance
|
||||||
# load the sequence and calculate the scheduled_at time
|
# load the sequence and calculate the scheduled_at time
|
||||||
Enum.map(regimen_instance.regimen.regimen_items, fn(%{time_offset: offset, sequence_id: sequence_id}) ->
|
Enum.map(regimen_instance.regimen.regimen_items, fn(%{time_offset: offset, sequence_id: sequence_id}) ->
|
||||||
scheduled_at = DateTime.add(regimen_instance.epoch, offset, :millisecond)
|
scheduled_at = DateTime.add(regimen_instance.epoch, offset, :millisecond)
|
||||||
sequence = Asset.get_sequence(sequence_id) || raise("sequence #{sequence_id} is not synced")
|
sequence = Asset.get_sequence(sequence_id) || raise("sequence #{sequence_id} is not synced")
|
||||||
%{scheduled_at: scheduled_at, sequence: sequence}
|
%{scheduled_at: scheduled_at, sequence: sequence}
|
||||||
end)
|
end)
|
||||||
# get rid of any item that has already been scheduled/executed
|
# get rid of any item that has already been scheduled/executed
|
||||||
|> Enum.reject(fn(%{scheduled_at: scheduled_at}) ->
|
|> Enum.reject(fn(%{scheduled_at: scheduled_at}) ->
|
||||||
Asset.get_regimen_instance_execution(regimen_instance, scheduled_at)
|
Asset.get_regimen_instance_execution(regimen_instance, scheduled_at)
|
||||||
end)
|
end)
|
||||||
|> Enum.each(fn(%{scheduled_at: at, sequence: sequence}) ->
|
|> Enum.each(fn(%{scheduled_at: at, sequence: sequence}) ->
|
||||||
schedule_sequence(regimen_instance, sequence, at)
|
schedule_sequence(regimen_instance, sequence, at)
|
||||||
end)
|
end)
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_info({FarmbotCeleryScript, {:scheduled_execution, scheduled_at, executed_at, result}}, state) do
|
def handle_info({FarmbotCeleryScript, {:scheduled_execution, scheduled_at, executed_at, result}}, state) do
|
||||||
status = case result do
|
status = case result do
|
||||||
:ok -> "ok"
|
:ok -> "ok"
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
FarmbotCore.Logger.error(2, "Regimen scheduled at #{scheduled_at} failed to execute: #{reason}")
|
FarmbotCore.Logger.error(2, "Regimen scheduled at #{scheduled_at} failed to execute: #{reason}")
|
||||||
reason
|
reason
|
||||||
end
|
end
|
||||||
|
@ -81,11 +79,11 @@ defimpl FarmbotCore.AssetWorker, for: FarmbotCore.Asset.RegimenInstance do
|
||||||
regimen_params = AST.decode(regimen_instance.regimen.body)
|
regimen_params = AST.decode(regimen_instance.regimen.body)
|
||||||
# there may be many sequence scopes from here downward
|
# there may be many sequence scopes from here downward
|
||||||
celery_ast = AST.decode(sequence)
|
celery_ast = AST.decode(sequence)
|
||||||
celery_args =
|
celery_args =
|
||||||
celery_ast.args
|
celery_ast.args
|
||||||
|> Map.put(:sequence_name, sequence.name)
|
|> Map.put(:sequence_name, sequence.name)
|
||||||
|> Map.put(:locals, %{celery_ast.args.locals | body: celery_ast.args.locals.body ++ regimen_params ++ farm_event_params})
|
|> Map.put(:locals, %{celery_ast.args.locals | body: celery_ast.args.locals.body ++ regimen_params ++ farm_event_params})
|
||||||
|
|
||||||
celery_ast = %{celery_ast | args: celery_args}
|
celery_ast = %{celery_ast | args: celery_args}
|
||||||
FarmbotCeleryScript.schedule(celery_ast, at, sequence)
|
FarmbotCeleryScript.schedule(celery_ast, at, sequence)
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,11 +6,16 @@ defmodule FarmbotCore.BotState.Supervisor do
|
||||||
end
|
end
|
||||||
|
|
||||||
def init([]) do
|
def init([]) do
|
||||||
children = [
|
Supervisor.init(children(), [strategy: :one_for_all])
|
||||||
|
end
|
||||||
|
|
||||||
|
def children do
|
||||||
|
default = [
|
||||||
FarmbotCore.BotState,
|
FarmbotCore.BotState,
|
||||||
FarmbotCore.BotState.FileSystem,
|
FarmbotCore.BotState.FileSystem,
|
||||||
FarmbotCore.BotState.SchedulerUsageReporter
|
FarmbotCore.BotState.SchedulerUsageReporter
|
||||||
]
|
]
|
||||||
Supervisor.init(children, [strategy: :one_for_all])
|
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||||
|
Keyword.get(config, :children, default)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,9 +7,12 @@ defmodule FarmbotCore.Config.Supervisor do
|
||||||
end
|
end
|
||||||
|
|
||||||
def init([]) do
|
def init([]) do
|
||||||
children = [
|
Supervisor.init(children(), strategy: :one_for_one)
|
||||||
{FarmbotCore.Config.Repo, []},
|
end
|
||||||
]
|
|
||||||
Supervisor.init(children, strategy: :one_for_one)
|
def children do
|
||||||
|
default = [ {FarmbotCore.Config.Repo, []} ]
|
||||||
|
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||||
|
Keyword.get(config, :children, default)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -77,21 +77,21 @@ defmodule FarmbotCore.FirmwareOpenTask do
|
||||||
{:noreply, increment_attempts(%{state | timer: timer})}
|
{:noreply, increment_attempts(%{state | timer: timer})}
|
||||||
|
|
||||||
firmware_hardware == "none" && needs_open? ->
|
firmware_hardware == "none" && needs_open? ->
|
||||||
FarmbotCore.Logger.debug 3, "Firmware needs to be closed"
|
FarmbotCore.Logger.debug 3, "Closing firmware..."
|
||||||
unswap_transport()
|
unswap_transport()
|
||||||
Config.update_config_value(:bool, "settings", "firmware_needs_open", false)
|
Config.update_config_value(:bool, "settings", "firmware_needs_open", false)
|
||||||
timer = Process.send_after(self(), :open, 5000)
|
timer = Process.send_after(self(), :open, 5000)
|
||||||
{:noreply, %{state | timer: timer, attempts: 0}}
|
{:noreply, %{state | timer: timer, attempts: 0}}
|
||||||
|
|
||||||
needs_open? ->
|
needs_open? ->
|
||||||
FarmbotCore.Logger.debug 3, "Firmware needs to be opened"
|
FarmbotCore.Logger.debug 3, "Opening firmware..."
|
||||||
case swap_transport(firmware_path) do
|
case swap_transport(firmware_path) do
|
||||||
:ok ->
|
:ok ->
|
||||||
Config.update_config_value(:bool, "settings", "firmware_needs_open", false)
|
Config.update_config_value(:bool, "settings", "firmware_needs_open", false)
|
||||||
timer = Process.send_after(self(), :open, 5000)
|
timer = Process.send_after(self(), :open, 5000)
|
||||||
{:noreply, %{state | timer: timer, attempts: 0}}
|
{:noreply, %{state | timer: timer, attempts: 0}}
|
||||||
other ->
|
other ->
|
||||||
FarmbotCore.Logger.debug 3, "Firmware failed to open: #{inspect(other)}"
|
FarmbotCore.Logger.debug 3, "Not ready to open yet, will retry in 5s (#{inspect(other)})"
|
||||||
timer = Process.send_after(self(), :open, 5000)
|
timer = Process.send_after(self(), :open, 5000)
|
||||||
{:noreply, %{state | timer: timer, attempts: 0}}
|
{:noreply, %{state | timer: timer, attempts: 0}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
defmodule FarmbotCore.Leds do
|
defmodule FarmbotCore.Leds do
|
||||||
@moduledoc "API for controling Farmbot LEDS."
|
@moduledoc "API for controling Farmbot LEDS."
|
||||||
@led_handler Application.get_env(:farmbot_core, __MODULE__)[:gpio_handler]
|
|
||||||
@led_handler || Mix.raise("You forgot a led handler!")
|
|
||||||
|
|
||||||
@valid_status [:off, :solid, :slow_blink, :fast_blink, :really_fast_blink]
|
@valid_status [:off, :solid, :slow_blink, :fast_blink, :really_fast_blink]
|
||||||
|
|
||||||
|
@ -15,29 +13,7 @@ defmodule FarmbotCore.Leds do
|
||||||
def white4(status) when status in @valid_status, do: led_handler().white4(status)
|
def white4(status) when status in @valid_status, do: led_handler().white4(status)
|
||||||
def white5(status) when status in @valid_status, do: led_handler().white5(status)
|
def white5(status) when status in @valid_status, do: led_handler().white5(status)
|
||||||
|
|
||||||
def factory_test(status) do
|
def led_handler,
|
||||||
red(:off)
|
|
||||||
blue(:off)
|
|
||||||
green(:off)
|
|
||||||
yellow(:off)
|
|
||||||
white1(:off)
|
|
||||||
white2(:off)
|
|
||||||
white3(:off)
|
|
||||||
white4(:off)
|
|
||||||
white5(:off)
|
|
||||||
|
|
||||||
red(status)
|
|
||||||
blue(status)
|
|
||||||
green(status)
|
|
||||||
yellow(status)
|
|
||||||
white1(status)
|
|
||||||
white2(status)
|
|
||||||
white3(status)
|
|
||||||
white4(status)
|
|
||||||
white5(status)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp led_handler,
|
|
||||||
do: Application.get_env(:farmbot_core, __MODULE__)[:gpio_handler]
|
do: Application.get_env(:farmbot_core, __MODULE__)[:gpio_handler]
|
||||||
|
|
||||||
def child_spec(opts) do
|
def child_spec(opts) do
|
||||||
|
|
|
@ -7,11 +7,13 @@ defmodule FarmbotCore.Logger.Supervisor do
|
||||||
end
|
end
|
||||||
|
|
||||||
def init([]) do
|
def init([]) do
|
||||||
children = [
|
|
||||||
supervisor(FarmbotCore.Logger.Repo, [])
|
|
||||||
]
|
|
||||||
|
|
||||||
opts = [strategy: :one_for_all]
|
opts = [strategy: :one_for_all]
|
||||||
supervise(children, opts)
|
supervise(children(), opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def children do
|
||||||
|
default = [supervisor(FarmbotCore.Logger.Repo, [])]
|
||||||
|
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||||
|
Keyword.get(config, :children, default)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,11 +10,16 @@ defmodule FarmbotCore.StorageSupervisor do
|
||||||
end
|
end
|
||||||
|
|
||||||
def init([]) do
|
def init([]) do
|
||||||
children = [
|
Supervisor.init(children(), [strategy: :one_for_one])
|
||||||
|
end
|
||||||
|
|
||||||
|
def children do
|
||||||
|
default = [
|
||||||
FarmbotCore.Logger.Supervisor,
|
FarmbotCore.Logger.Supervisor,
|
||||||
FarmbotCore.Config.Supervisor,
|
FarmbotCore.Config.Supervisor,
|
||||||
FarmbotCore.Asset.Supervisor
|
FarmbotCore.Asset.Supervisor
|
||||||
]
|
]
|
||||||
Supervisor.init(children, [strategy: :one_for_one])
|
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||||
|
Keyword.get(config, :children, default)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,21 +1,6 @@
|
||||||
defmodule FarmbotCore.TimeUtils do
|
defmodule FarmbotCore.TimeUtils do
|
||||||
@moduledoc "Helper functions for working with time."
|
@moduledoc "Helper functions for working with time."
|
||||||
|
|
||||||
def format_time(%DateTime{} = dt) do
|
|
||||||
"#{format_num(dt.month)}/#{format_num(dt.day)}/#{dt.year} " <>
|
|
||||||
"at #{format_num(dt.hour)}:#{format_num(dt.minute)}"
|
|
||||||
end
|
|
||||||
|
|
||||||
defp format_num(num), do: :io_lib.format('~2..0B', [num]) |> to_string
|
|
||||||
|
|
||||||
# returns midnight of today
|
|
||||||
@spec build_epoch(DateTime.t) :: DateTime.t
|
|
||||||
def build_epoch(time) do
|
|
||||||
tz = FarmbotCore.Asset.fbos_config().timezone
|
|
||||||
n = Timex.Timezone.convert(time, tz)
|
|
||||||
Timex.shift(n, hours: -n.hour, seconds: -n.second, minutes: -n.minute)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Compares a datetime with another.
|
Compares a datetime with another.
|
||||||
• -1 -- the first date comes before the second one
|
• -1 -- the first date comes before the second one
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
defmodule FarmbotCore.Asset.Repo.Migrations.AddPulloutDirectionToPoint do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table("points") do
|
||||||
|
# 0 means "NONE"
|
||||||
|
add(:pullout_direction, :integer, default: 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
execute("UPDATE points SET updated_at = \"1970-11-07 16:52:31.618000\"")
|
||||||
|
end
|
||||||
|
end
|
|
@ -14,22 +14,16 @@ defmodule FarmbotCore.Config.Repo.Migrations.AddNtpAndDnsConfigs do
|
||||||
:default_dns_name
|
:default_dns_name
|
||||||
]
|
]
|
||||||
|
|
||||||
@config_error """
|
unless @default_ntp_server_1 && @default_ntp_server_2 && @default_dns_name do
|
||||||
config :farmbot_core, FarmbotCore.EctoMigrator, [
|
@config_error """
|
||||||
default_ntp_server_1: "0.pool.ntp.org",
|
config :farmbot_core, FarmbotCore.EctoMigrator, [
|
||||||
default_ntp_server_2: "1.pool.ntp.org",
|
default_ntp_server_1: "0.pool.ntp.org",
|
||||||
default_dns_name: "my.farm.bot"
|
default_ntp_server_2: "1.pool.ntp.org",
|
||||||
]
|
default_dns_name: "my.farm.bot"
|
||||||
"""
|
]
|
||||||
|
"""
|
||||||
if is_nil(@default_ntp_server_1),
|
Mix.raise(@config_error)
|
||||||
do: raise(@config_error)
|
end
|
||||||
|
|
||||||
if is_nil(@default_ntp_server_2),
|
|
||||||
do: raise(@config_error)
|
|
||||||
|
|
||||||
if is_nil(@default_dns_name),
|
|
||||||
do: raise(@config_error)
|
|
||||||
|
|
||||||
def change do
|
def change do
|
||||||
create_settings_config(
|
create_settings_config(
|
||||||
|
|
|
@ -27,6 +27,7 @@ defmodule FarmbotCore.Asset.CommandTest do
|
||||||
:ok = Command.update(FirmwareConfig, 23, Map.from_struct(config))
|
:ok = Command.update(FirmwareConfig, 23, Map.from_struct(config))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "update / destroy fbos config" do
|
test "update / destroy fbos config" do
|
||||||
params = %{id: 23, update_channel: "whatever"}
|
params = %{id: 23, update_channel: "whatever"}
|
||||||
:ok = Command.update(FbosConfig, 23, params)
|
:ok = Command.update(FbosConfig, 23, params)
|
||||||
|
@ -38,6 +39,7 @@ defmodule FarmbotCore.Asset.CommandTest do
|
||||||
refute next_config
|
refute next_config
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "update / destroy device" do
|
test "update / destroy device" do
|
||||||
params = %{id: 23, name: "Old Device"}
|
params = %{id: 23, name: "Old Device"}
|
||||||
:ok = Command.update(Device, 23, params)
|
:ok = Command.update(Device, 23, params)
|
||||||
|
@ -56,6 +58,7 @@ defmodule FarmbotCore.Asset.CommandTest do
|
||||||
assert Asset.get_regimen(id)
|
assert Asset.get_regimen(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "update regimen" do
|
test "update regimen" do
|
||||||
id = id()
|
id = id()
|
||||||
:ok = Command.update("Regimen", id, %{id: id, name: "abc", monitor: false})
|
:ok = Command.update("Regimen", id, %{id: id, name: "abc", monitor: false})
|
||||||
|
@ -70,6 +73,7 @@ defmodule FarmbotCore.Asset.CommandTest do
|
||||||
refute Asset.get_regimen(id)
|
refute Asset.get_regimen(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "insert new farm_event" do
|
test "insert new farm_event" do
|
||||||
id = id()
|
id = id()
|
||||||
:ok = Command.update("FarmEvent", id, %{id: id, monitor: false})
|
:ok = Command.update("FarmEvent", id, %{id: id, monitor: false})
|
||||||
|
@ -101,6 +105,7 @@ defmodule FarmbotCore.Asset.CommandTest do
|
||||||
assert Asset.get_farm_event(id).executable_type == "Regimen"
|
assert Asset.get_farm_event(id).executable_type == "Regimen"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "delete farm_event" do
|
test "delete farm_event" do
|
||||||
id = id()
|
id = id()
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
|
||||||
|
|
||||||
@fake_point_group %PointGroup{
|
@fake_point_group %PointGroup{
|
||||||
criteria: %{
|
criteria: %{
|
||||||
"day" => %{"op" => "<", "days_ago" => 4},
|
"day" => %{"op" => ">", "days_ago" => 4},
|
||||||
"string_eq" => %{
|
"string_eq" => %{
|
||||||
"openfarm_slug" => ["five", "nine"],
|
"openfarm_slug" => ["five", "nine"],
|
||||||
"meta.created_by" => ["plant-detection"]
|
"meta.created_by" => ["plant-detection"]
|
||||||
|
@ -24,6 +24,23 @@ defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@simple_point_group %PointGroup{
|
||||||
|
point_ids: [],
|
||||||
|
sort_type: "xy_ascending",
|
||||||
|
criteria: %{
|
||||||
|
"day" => %{
|
||||||
|
"op" => "<",
|
||||||
|
"days_ago" => 0
|
||||||
|
},
|
||||||
|
"string_eq" => %{
|
||||||
|
"pointer_type" => ["Plant"]
|
||||||
|
},
|
||||||
|
"number_eq" => %{},
|
||||||
|
"number_lt" => %{},
|
||||||
|
"number_gt" => %{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Use this is a fake "Timex.now()" value when mocking.
|
# Use this is a fake "Timex.now()" value when mocking.
|
||||||
@now ~U[2222-12-10 02:22:22.222222Z]
|
@now ~U[2222-12-10 02:22:22.222222Z]
|
||||||
@five_days_ago ~U[2222-12-05 01:11:11.111111Z]
|
@five_days_ago ~U[2222-12-05 01:11:11.111111Z]
|
||||||
|
@ -90,6 +107,19 @@ defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
|
||||||
pg
|
pg
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "direct match on `pointer_type` via `string_eq`" do
|
||||||
|
Repo.delete_all(PointGroup)
|
||||||
|
Repo.delete_all(Point)
|
||||||
|
|
||||||
|
point!(%{id: 1, pointer_type: "Plant"})
|
||||||
|
point!(%{id: 2, pointer_type: "Weed"})
|
||||||
|
point!(%{id: 3, pointer_type: "ToolSlot"})
|
||||||
|
point!(%{id: 4, pointer_type: "GenericPointer"})
|
||||||
|
|
||||||
|
result = CriteriaRetriever.run(@simple_point_group)
|
||||||
|
assert Enum.count(result) == 1
|
||||||
|
end
|
||||||
|
|
||||||
test "run/1" do
|
test "run/1" do
|
||||||
expect(Timex, :now, fn -> @now end)
|
expect(Timex, :now, fn -> @now end)
|
||||||
pg = point_group_with_fake_points()
|
pg = point_group_with_fake_points()
|
||||||
|
@ -123,6 +153,7 @@ defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
|
||||||
Enum.map(expected, fn id -> assert Enum.member?(results, id) end)
|
Enum.map(expected, fn id -> assert Enum.member?(results, id) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "point group that does not define criteria" do
|
test "point group that does not define criteria" do
|
||||||
Repo.delete_all(PointGroup)
|
Repo.delete_all(PointGroup)
|
||||||
Repo.delete_all(Point)
|
Repo.delete_all(Point)
|
||||||
|
@ -451,7 +482,6 @@ defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
|
||||||
"string_eq" => %{}
|
"string_eq" => %{}
|
||||||
},
|
},
|
||||||
id: 201,
|
id: 201,
|
||||||
local_id: "30856f5e-1f97-4e18-b5e0-84dc7cd9bbf0",
|
|
||||||
name: "Test (Broke?)",
|
name: "Test (Broke?)",
|
||||||
point_ids: whitelist,
|
point_ids: whitelist,
|
||||||
sort_type: "xy_ascending",
|
sort_type: "xy_ascending",
|
||||||
|
@ -463,4 +493,70 @@ defmodule FarmbotCore.Asset.CriteriaRetrieverTest do
|
||||||
assert Enum.count(whitelist) == Enum.count(results)
|
assert Enum.count(whitelist) == Enum.count(results)
|
||||||
Enum.map(whitelist, fn id -> assert Enum.member?(results, id) end)
|
Enum.map(whitelist, fn id -> assert Enum.member?(results, id) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "edge case: Filter by crop type" do
|
||||||
|
Repo.delete_all(PointGroup)
|
||||||
|
Repo.delete_all(Point)
|
||||||
|
ok = point!(%{id: 1, pointer_type: "Plant", openfarm_slug: "spinach"})
|
||||||
|
point!(%{id: 2, pointer_type: "Plant", openfarm_slug: "beetroot"})
|
||||||
|
point!(%{id: 3, pointer_type: "Weed", openfarm_slug: "thistle"})
|
||||||
|
point!(%{id: 4, pointer_type: "Weed", openfarm_slug: "spinach"})
|
||||||
|
|
||||||
|
pg = %PointGroup{
|
||||||
|
:id => 241,
|
||||||
|
:point_ids => [],
|
||||||
|
:criteria => %{
|
||||||
|
"day" => %{
|
||||||
|
"op" => "<",
|
||||||
|
"days_ago" => 0
|
||||||
|
},
|
||||||
|
"string_eq" => %{
|
||||||
|
"pointer_type" => ["Plant"],
|
||||||
|
"openfarm_slug" => ["spinach"]
|
||||||
|
},
|
||||||
|
"number_eq" => %{},
|
||||||
|
"number_lt" => %{},
|
||||||
|
"number_gt" => %{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ids = CriteriaRetriever.run(pg) |> Enum.map(fn p -> p.id end)
|
||||||
|
assert Enum.member?(ids, ok.id)
|
||||||
|
assert Enum.count(ids) == 1
|
||||||
|
end
|
||||||
|
|
||||||
|
test "edge case: Filter by slot direction" do
|
||||||
|
Repo.delete_all(PointGroup)
|
||||||
|
Repo.delete_all(Point)
|
||||||
|
|
||||||
|
ok = point!(%{id: 1, pointer_type: "ToolSlot", pullout_direction: 3})
|
||||||
|
point!(%{id: 2, pointer_type: "Weed", pullout_direction: 3})
|
||||||
|
point!(%{id: 3, pointer_type: "ToolSlot", pullout_direction: 4})
|
||||||
|
point!(%{id: 4, pointer_type: "GenericPointer", pullout_direction: 2})
|
||||||
|
|
||||||
|
pg = %PointGroup{
|
||||||
|
:id => 242,
|
||||||
|
:name => "Filter by slot direction",
|
||||||
|
:point_ids => [],
|
||||||
|
:sort_type => "xy_ascending",
|
||||||
|
:criteria => %{
|
||||||
|
"day" => %{
|
||||||
|
"op" => "<",
|
||||||
|
"days_ago" => 0
|
||||||
|
},
|
||||||
|
"string_eq" => %{
|
||||||
|
"pointer_type" => ["ToolSlot"]
|
||||||
|
},
|
||||||
|
"number_eq" => %{
|
||||||
|
"pullout_direction" => [3]
|
||||||
|
},
|
||||||
|
"number_lt" => %{},
|
||||||
|
"number_gt" => %{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ids = CriteriaRetriever.run(pg) |> Enum.map(fn p -> p.id end)
|
||||||
|
assert Enum.member?(ids, ok.id)
|
||||||
|
assert Enum.count(ids) == 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
defmodule FarmbotCore.Asset.PrivateTest do
|
|
||||||
use ExUnit.Case, async: true
|
|
||||||
end
|
|
|
@ -1,61 +0,0 @@
|
||||||
defmodule FarmbotCore.AssetMonitorTest do
|
|
||||||
use ExUnit.Case, async: false
|
|
||||||
alias FarmbotCore.{Asset.Repo, AssetMonitor, AssetSupervisor}
|
|
||||||
import Farmbot.TestSupport.AssetFixtures
|
|
||||||
|
|
||||||
describe "regimen instances" do
|
|
||||||
test "adding a regimen instance starts a process" do
|
|
||||||
farm_event_params = %{
|
|
||||||
start_time: DateTime.utc_now(),
|
|
||||||
end_time: DateTime.utc_now(),
|
|
||||||
repeat: 1,
|
|
||||||
time_unit: "never"
|
|
||||||
}
|
|
||||||
|
|
||||||
pr = regimen_instance(%{}, farm_event_params, %{monitor: true})
|
|
||||||
|
|
||||||
AssetMonitor.force_checkup()
|
|
||||||
|
|
||||||
assert {id, _, _, _} = AssetSupervisor.whereis_child(pr)
|
|
||||||
assert id == pr.local_id
|
|
||||||
|
|
||||||
Repo.delete!(pr)
|
|
||||||
|
|
||||||
AssetMonitor.force_checkup()
|
|
||||||
|
|
||||||
assert {id, :undefined, _, _} = AssetSupervisor.whereis_child(pr)
|
|
||||||
assert id == pr.local_id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "farm events" do
|
|
||||||
test "adding a farm event starts a process" do
|
|
||||||
seq = sequence()
|
|
||||||
now = DateTime.utc_now()
|
|
||||||
start_time = Timex.shift(now, minutes: -20)
|
|
||||||
end_time = Timex.shift(now, minutes: 10)
|
|
||||||
|
|
||||||
params = %{
|
|
||||||
monitor: true,
|
|
||||||
start_time: start_time,
|
|
||||||
end_time: end_time,
|
|
||||||
repeat: 5,
|
|
||||||
time_unit: "hourly"
|
|
||||||
}
|
|
||||||
|
|
||||||
event = sequence_event(seq, params)
|
|
||||||
|
|
||||||
AssetMonitor.force_checkup()
|
|
||||||
|
|
||||||
assert {id, _, _, _} = AssetSupervisor.whereis_child(event)
|
|
||||||
assert id == event.local_id
|
|
||||||
|
|
||||||
Repo.delete!(event)
|
|
||||||
|
|
||||||
AssetMonitor.force_checkup()
|
|
||||||
|
|
||||||
assert {id, :undefined, _, _} = AssetSupervisor.whereis_child(event)
|
|
||||||
assert id == event.local_id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -15,4 +15,10 @@ defmodule FarmbotCore.AssetTest do
|
||||||
assert %RegimenInstance{} = Asset.new_regimen_instance!(event)
|
assert %RegimenInstance{} = Asset.new_regimen_instance!(event)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "Asset.device/1" do
|
||||||
|
assert nil == Asset.device(:ota_hour)
|
||||||
|
assert %FarmbotCore.Asset.Device{} = Asset.update_device!(%{ota_hour: 17})
|
||||||
|
assert 17 == Asset.device(:ota_hour)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@ defmodule FarmbotCore.FbosConfigWorkerTest do
|
||||||
|
|
||||||
import Farmbot.TestSupport.AssetFixtures
|
import Farmbot.TestSupport.AssetFixtures
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "adds configs to bot state and config_storage" do
|
test "adds configs to bot state and config_storage" do
|
||||||
%FbosConfig{} =
|
%FbosConfig{} =
|
||||||
conf =
|
conf =
|
||||||
|
|
|
@ -61,6 +61,8 @@ defmodule FarmbotCore.BotState.FileSystemTest do
|
||||||
|
|
||||||
describe "server" do
|
describe "server" do
|
||||||
test "serializes state to fs" do
|
test "serializes state to fs" do
|
||||||
|
IO.puts("THIS TEST BLINKS! Fix it.")
|
||||||
|
|
||||||
root_dir =
|
root_dir =
|
||||||
Path.join([
|
Path.join([
|
||||||
System.tmp_dir!(),
|
System.tmp_dir!(),
|
||||||
|
@ -81,7 +83,7 @@ defmodule FarmbotCore.BotState.FileSystemTest do
|
||||||
:ok = BotState.set_pin_value(bot_state_pid, 1, 1)
|
:ok = BotState.set_pin_value(bot_state_pid, 1, 1)
|
||||||
assert_received {BotState, _}, 200
|
assert_received {BotState, _}, 200
|
||||||
# sleep to allow changes to propagate.
|
# sleep to allow changes to propagate.
|
||||||
Process.sleep(200)
|
Process.sleep(2000)
|
||||||
pins_dir = Path.join([root_dir, "pins", "1"])
|
pins_dir = Path.join([root_dir, "pins", "1"])
|
||||||
# default value
|
# default value
|
||||||
assert File.read!(Path.join(pins_dir, "mode")) == "-1"
|
assert File.read!(Path.join(pins_dir, "mode")) == "-1"
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule FarmbotCore.BotStateTest do
|
||||||
assert_receive {BotState, %Ecto.Changeset{valid?: true}}
|
assert_receive {BotState, %Ecto.Changeset{valid?: true}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "invalid data doesn't get dispatched" do
|
test "invalid data doesn't get dispatched" do
|
||||||
{:ok, bot_state_pid} = BotState.start_link([], [])
|
{:ok, bot_state_pid} = BotState.start_link([], [])
|
||||||
_initial_state = BotState.subscribe(bot_state_pid)
|
_initial_state = BotState.subscribe(bot_state_pid)
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
defmodule FarmbotCore.FarmwareRuntime.PipeWorkerTest do
|
|
||||||
use ExUnit.Case, async: false
|
|
||||||
# alias FarmbotCore.FarmwareRuntime.PipeWorker
|
|
||||||
|
|
||||||
# TODO Find a suitable tool for testing domain sockets?
|
|
||||||
|
|
||||||
# test "reads data from pipe" do
|
|
||||||
# pipe_name = random_pipe()
|
|
||||||
# {:ok, pipe_worker} = PipeWorker.start_link(pipe_name)
|
|
||||||
# ref = PipeWorker.read(pipe_worker, 11)
|
|
||||||
# {_, 0} = System.cmd("bash", ["-c", "echo -e 'hello world' > #{pipe_name}"])
|
|
||||||
# assert_receive {PipeWorker, ^ref, {:ok, "hello world"}}
|
|
||||||
# end
|
|
||||||
|
|
||||||
# test "writes data to a pipe" do
|
|
||||||
# pipe_name = random_pipe()
|
|
||||||
# {:ok, pipe_worker} = PipeWorker.start_link(pipe_name)
|
|
||||||
|
|
||||||
# ref = PipeWorker.read(pipe_worker, 11)
|
|
||||||
# PipeWorker.write(pipe_worker, "hello world")
|
|
||||||
# assert_receive {PipeWorker, ^ref, {:ok, "hello world"}}
|
|
||||||
# end
|
|
||||||
|
|
||||||
# test "cleanup pipes on exit" do
|
|
||||||
# pipe_name = random_pipe()
|
|
||||||
# {:ok, pipe_worker} = PipeWorker.start_link(pipe_name)
|
|
||||||
# assert File.exists?(pipe_name)
|
|
||||||
# _ = Process.flag(:trap_exit, true)
|
|
||||||
# :ok = PipeWorker.close(pipe_worker)
|
|
||||||
# assert_receive {:EXIT, ^pipe_worker, :normal}
|
|
||||||
# refute File.exists?(pipe_name)
|
|
||||||
# end
|
|
||||||
|
|
||||||
# defp random_pipe do
|
|
||||||
# pipe_name = Ecto.UUID.generate() <> ".pipe"
|
|
||||||
# Path.join([System.tmp_dir!(), pipe_name])
|
|
||||||
# end
|
|
||||||
end
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
defmodule FarmbotCore.Leds.StubHandlerTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
import ExUnit.CaptureIO
|
||||||
|
|
||||||
|
@color_map %{
|
||||||
|
:red => :red,
|
||||||
|
:blue => :blue,
|
||||||
|
:green => :green,
|
||||||
|
:yellow => :yellow,
|
||||||
|
:white1 => :white,
|
||||||
|
:white2 => :white,
|
||||||
|
:white3 => :white,
|
||||||
|
:white4 => :white,
|
||||||
|
:white5 => :white
|
||||||
|
}
|
||||||
|
@status [:fast_blink, :really_fast_blink, :slow_blink, :solid]
|
||||||
|
|
||||||
|
def capture_led(color) do
|
||||||
|
status = @status |> Enum.shuffle() |> Enum.at(0)
|
||||||
|
do_it = fn -> apply(FarmbotCore.Leds, color, [status]) end
|
||||||
|
cap = capture_io(do_it)
|
||||||
|
assert cap =~ "LED STATUS:"
|
||||||
|
assert cap =~ apply(IO.ANSI, Map.fetch!(@color_map, color), [])
|
||||||
|
end
|
||||||
|
|
||||||
|
test "leds" do
|
||||||
|
capture_led(:red)
|
||||||
|
capture_led(:blue)
|
||||||
|
capture_led(:green)
|
||||||
|
capture_led(:yellow)
|
||||||
|
capture_led(:white1)
|
||||||
|
capture_led(:white2)
|
||||||
|
capture_led(:white3)
|
||||||
|
capture_led(:white4)
|
||||||
|
capture_led(:white5)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule FarmbotCore.LogTest do
|
||||||
|
alias FarmbotCore.Log
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
test "to_chars" do
|
||||||
|
log = %Log{message: "Hello, world!"}
|
||||||
|
assert "Hello, world!" = "#{log}"
|
||||||
|
end
|
||||||
|
end
|
|
@ -2,6 +2,7 @@ defmodule FarmbotCore.LoggerTest do
|
||||||
use ExUnit.Case
|
use ExUnit.Case
|
||||||
require FarmbotCore.Logger
|
require FarmbotCore.Logger
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "allows handling a log more than once by re-inserting it." do
|
test "allows handling a log more than once by re-inserting it." do
|
||||||
log = FarmbotCore.Logger.debug(1, "Test log ABC")
|
log = FarmbotCore.Logger.debug(1, "Test log ABC")
|
||||||
# Handling a log should delete it from the store.
|
# Handling a log should delete it from the store.
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
defmodule FarmbotCore.ProjectTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
# @opts [cd: Path.join("c_src", "farmbot-arduino-firmware")]
|
||||||
|
|
||||||
|
test "arduino_commit" do
|
||||||
|
actual = FarmbotCore.Project.arduino_commit()
|
||||||
|
assert is_binary(actual)
|
||||||
|
assert String.length(actual) == 40
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,5 +1,16 @@
|
||||||
use Mix.Config
|
use Mix.Config
|
||||||
|
|
||||||
if Mix.env() == :test do
|
if Mix.env() == :test do
|
||||||
config :farmbot_ext, FarmbotExt, children: []
|
config :ex_unit, capture_logs: true
|
||||||
|
mapper = fn mod -> config :farmbot_ext, mod, children: [] end
|
||||||
|
|
||||||
|
list = [
|
||||||
|
FarmbotExt,
|
||||||
|
FarmbotExt.AMQP.ChannelSupervisor,
|
||||||
|
FarmbotExt.API.DirtyWorker.Supervisor,
|
||||||
|
FarmbotExt.API.EagerLoader.Supervisor,
|
||||||
|
FarmbotExt.Bootstrap.Supervisor
|
||||||
|
]
|
||||||
|
|
||||||
|
Enum.map(list, mapper)
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,13 +4,11 @@ defmodule FarmbotExt do
|
||||||
use Application
|
use Application
|
||||||
|
|
||||||
def start(_type, _args) do
|
def start(_type, _args) do
|
||||||
opts = [strategy: :one_for_one, name: __MODULE__]
|
Supervisor.start_link(children(), opts())
|
||||||
Supervisor.start_link(children(), opts)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# This only exists because I was getting too many crashed
|
def opts, do: [strategy: :one_for_one, name: __MODULE__]
|
||||||
# supervisor reports in the test suite (distraction from
|
|
||||||
# real test failures).
|
|
||||||
def children do
|
def children do
|
||||||
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||||
Keyword.get(config, :children, [FarmbotExt.Bootstrap])
|
Keyword.get(config, :children, [FarmbotExt.Bootstrap])
|
||||||
|
|
|
@ -19,17 +19,19 @@ defmodule FarmbotExt.AMQP.ChannelSupervisor do
|
||||||
end
|
end
|
||||||
|
|
||||||
def init([token]) do
|
def init([token]) do
|
||||||
jwt = JWT.decode!(token)
|
Supervisor.init(children(JWT.decode!(token)), strategy: :one_for_one)
|
||||||
|
end
|
||||||
|
|
||||||
children = [
|
def children(jwt) do
|
||||||
|
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||||
|
|
||||||
|
Keyword.get(config, :children, [
|
||||||
{TelemetryChannel, [jwt: jwt]},
|
{TelemetryChannel, [jwt: jwt]},
|
||||||
{LogChannel, [jwt: jwt]},
|
{LogChannel, [jwt: jwt]},
|
||||||
{PingPongChannel, [jwt: jwt]},
|
{PingPongChannel, [jwt: jwt]},
|
||||||
{BotStateChannel, [jwt: jwt]},
|
{BotStateChannel, [jwt: jwt]},
|
||||||
{AutoSyncChannel, [jwt: jwt]},
|
{AutoSyncChannel, [jwt: jwt]},
|
||||||
{CeleryScriptChannel, [jwt: jwt]}
|
{CeleryScriptChannel, [jwt: jwt]}
|
||||||
]
|
])
|
||||||
|
|
||||||
Supervisor.init(children, strategy: :one_for_one)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,7 @@ defmodule FarmbotExt.AMQP.PingPongChannel do
|
||||||
|
|
||||||
Also has a ~15-20 minute timer that will do an `HTTP` request
|
Also has a ~15-20 minute timer that will do an `HTTP` request
|
||||||
to `/api/device`. This refreshed the `last_seen_api` field which
|
to `/api/device`. This refreshed the `last_seen_api` field which
|
||||||
is required for devices that have `auto_sync` enabled as with
|
is required for devices that have `auto_sync` enabled as with
|
||||||
that field enabled, the device would never do an HTTP request
|
that field enabled, the device would never do an HTTP request
|
||||||
"""
|
"""
|
||||||
use GenServer
|
use GenServer
|
||||||
|
@ -99,9 +99,9 @@ defmodule FarmbotExt.AMQP.PingPongChannel do
|
||||||
http_ping_timer = Process.send_after(self(), :http_ping, ms)
|
http_ping_timer = Process.send_after(self(), :http_ping, ms)
|
||||||
{:noreply, %{state | http_ping_timer: http_ping_timer, ping_fails: 0}}
|
{:noreply, %{state | http_ping_timer: http_ping_timer, ping_fails: 0}}
|
||||||
|
|
||||||
_ ->
|
error ->
|
||||||
ping_fails = state.ping_fails + 1
|
ping_fails = state.ping_fails + 1
|
||||||
FarmbotCore.Logger.error(3, "Ping failed (#{ping_fails})")
|
FarmbotCore.Logger.error(3, "Ping failed (#{ping_fails}). #{inspect(error)}")
|
||||||
_ = Leds.blue(:off)
|
_ = Leds.blue(:off)
|
||||||
http_ping_timer = Process.send_after(self(), :http_ping, ms)
|
http_ping_timer = Process.send_after(self(), :http_ping, ms)
|
||||||
{:noreply, %{state | http_ping_timer: http_ping_timer, ping_fails: ping_fails}}
|
{:noreply, %{state | http_ping_timer: http_ping_timer, ping_fails: ping_fails}}
|
||||||
|
|
|
@ -10,14 +10,17 @@ defmodule FarmbotExt.AMQP.Supervisor do
|
||||||
end
|
end
|
||||||
|
|
||||||
def init([]) do
|
def init([]) do
|
||||||
|
Supervisor.init(children(), strategy: :one_for_all)
|
||||||
|
end
|
||||||
|
|
||||||
|
def children do
|
||||||
token = get_config_value(:string, "authorization", "token")
|
token = get_config_value(:string, "authorization", "token")
|
||||||
email = get_config_value(:string, "authorization", "email")
|
email = get_config_value(:string, "authorization", "email")
|
||||||
|
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||||
|
|
||||||
children = [
|
Keyword.get(config, :children, [
|
||||||
{FarmbotExt.AMQP.ConnectionWorker, [token: token, email: email]},
|
{FarmbotExt.AMQP.ConnectionWorker, [token: token, email: email]},
|
||||||
{FarmbotExt.AMQP.ChannelSupervisor, [token]}
|
{FarmbotExt.AMQP.ChannelSupervisor, [token]}
|
||||||
]
|
])
|
||||||
|
|
||||||
Supervisor.init(children, strategy: :one_for_all)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,7 +33,13 @@ defmodule FarmbotExt.API.DirtyWorker.Supervisor do
|
||||||
|
|
||||||
@impl Supervisor
|
@impl Supervisor
|
||||||
def init(_args) do
|
def init(_args) do
|
||||||
children = [
|
Supervisor.init(children(), strategy: :one_for_one)
|
||||||
|
end
|
||||||
|
|
||||||
|
def children do
|
||||||
|
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||||
|
|
||||||
|
Keyword.get(config, :children, [
|
||||||
{DirtyWorker, Device},
|
{DirtyWorker, Device},
|
||||||
{DirtyWorker, DeviceCert},
|
{DirtyWorker, DeviceCert},
|
||||||
{DirtyWorker, FbosConfig},
|
{DirtyWorker, FbosConfig},
|
||||||
|
@ -50,8 +56,6 @@ defmodule FarmbotExt.API.DirtyWorker.Supervisor do
|
||||||
{DirtyWorker, Sensor},
|
{DirtyWorker, Sensor},
|
||||||
{DirtyWorker, Sequence},
|
{DirtyWorker, Sequence},
|
||||||
{DirtyWorker, Tool}
|
{DirtyWorker, Tool}
|
||||||
]
|
])
|
||||||
|
|
||||||
Supervisor.init(children, strategy: :one_for_one)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,7 +39,13 @@ defmodule FarmbotExt.API.EagerLoader.Supervisor do
|
||||||
|
|
||||||
@impl Supervisor
|
@impl Supervisor
|
||||||
def init(_args) do
|
def init(_args) do
|
||||||
children = [
|
Supervisor.init(children(), strategy: :one_for_one)
|
||||||
|
end
|
||||||
|
|
||||||
|
def children do
|
||||||
|
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||||
|
|
||||||
|
Keyword.get(config, :children, [
|
||||||
{EagerLoader, Device},
|
{EagerLoader, Device},
|
||||||
{EagerLoader, FarmEvent},
|
{EagerLoader, FarmEvent},
|
||||||
{EagerLoader, FarmwareEnv},
|
{EagerLoader, FarmwareEnv},
|
||||||
|
@ -56,8 +62,6 @@ defmodule FarmbotExt.API.EagerLoader.Supervisor do
|
||||||
{EagerLoader, Sensor},
|
{EagerLoader, Sensor},
|
||||||
{EagerLoader, Sequence},
|
{EagerLoader, Sequence},
|
||||||
{EagerLoader, Tool}
|
{EagerLoader, Tool}
|
||||||
]
|
])
|
||||||
|
|
||||||
Supervisor.init(children, strategy: :one_for_one)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,7 @@ defmodule FarmbotExt.API.ImageUploader do
|
||||||
GenServer.start_link(__MODULE__, args, name: __MODULE__)
|
GenServer.start_link(__MODULE__, args, name: __MODULE__)
|
||||||
end
|
end
|
||||||
|
|
||||||
def force_checkup do
|
def force_checkup() do
|
||||||
GenServer.cast(__MODULE__, :force_checkup)
|
GenServer.cast(__MODULE__, :force_checkup)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -45,19 +45,23 @@ defmodule FarmbotExt.API.ImageUploader do
|
||||||
|
|
||||||
def handle_continue([], state), do: {:noreply, state, @checkup_time_ms}
|
def handle_continue([], state), do: {:noreply, state, @checkup_time_ms}
|
||||||
|
|
||||||
# the meta here is likely inaccurate here because of
|
# the meta here is likely inaccurate here because of pulling the location data
|
||||||
# pulling the location data from the cache instead of from the firmware
|
# from the cache instead of from the firmware directly. It's close enough and
|
||||||
# directly. It's close enough and getting data from the firmware directly
|
# getting data from the firmware directly would require more work than it is
|
||||||
# would require more work than it is worth
|
# worth
|
||||||
defp try_upload(image_filename) do
|
defp try_upload(image_filename) do
|
||||||
%{x: x, y: y, z: z} = BotState.fetch().location_data.position
|
%{x: x, y: y, z: z} = BotState.fetch().location_data.position
|
||||||
meta = %{x: x, y: y, z: z, name: Path.rootname(image_filename)}
|
meta = %{x: x, y: y, z: z, name: Path.rootname(image_filename)}
|
||||||
|
finalize(image_filename, API.upload_image(image_filename, meta))
|
||||||
|
end
|
||||||
|
|
||||||
with {:ok, %{status: s, body: _body}} when s > 199 and s < 300 <-
|
defp finalize(file, {:ok, %{status: s, body: _}}) when s > 199 and s < 300 do
|
||||||
API.upload_image(image_filename, meta) do
|
FarmbotCore.Logger.success(3, "Uploaded image: #{file}")
|
||||||
FarmbotCore.Logger.success(3, "Uploaded image: #{image_filename}")
|
File.rm(file)
|
||||||
File.rm(image_filename)
|
end
|
||||||
end
|
|
||||||
|
defp finalize(fname, other) do
|
||||||
|
FarmbotCore.Logger.error(3, "Upload Error (#{fname}): #{inspect(other)}")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Stolen from
|
# Stolen from
|
||||||
|
|
|
@ -12,14 +12,18 @@ defmodule FarmbotExt.Bootstrap.Supervisor do
|
||||||
|
|
||||||
@impl Supervisor
|
@impl Supervisor
|
||||||
def init([]) do
|
def init([]) do
|
||||||
children = [
|
Supervisor.init(children(), strategy: :one_for_one)
|
||||||
|
end
|
||||||
|
|
||||||
|
def children() do
|
||||||
|
config = Application.get_env(:farmbot_ext, __MODULE__) || []
|
||||||
|
|
||||||
|
Keyword.get(config, :children, [
|
||||||
FarmbotExt.API.EagerLoader.Supervisor,
|
FarmbotExt.API.EagerLoader.Supervisor,
|
||||||
FarmbotExt.API.DirtyWorker.Supervisor,
|
FarmbotExt.API.DirtyWorker.Supervisor,
|
||||||
FarmbotExt.AMQP.Supervisor,
|
FarmbotExt.AMQP.Supervisor,
|
||||||
FarmbotExt.API.ImageUploader,
|
FarmbotExt.API.ImageUploader,
|
||||||
FarmbotExt.Bootstrap.DropPasswordTask
|
FarmbotExt.Bootstrap.DropPasswordTask
|
||||||
]
|
])
|
||||||
|
|
||||||
Supervisor.init(children, strategy: :one_for_one)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
defmodule AutoSyncAssetHandlerTest do
|
defmodule AutoSyncAssetHandlerTest do
|
||||||
use ExUnit.Case, async: true
|
require Helpers
|
||||||
|
use ExUnit.Case, async: false
|
||||||
use Mimic
|
use Mimic
|
||||||
|
|
||||||
setup :verify_on_exit!
|
setup :verify_on_exit!
|
||||||
|
@ -8,7 +9,10 @@ defmodule AutoSyncAssetHandlerTest do
|
||||||
alias FarmbotExt.AMQP.AutoSyncAssetHandler
|
alias FarmbotExt.AMQP.AutoSyncAssetHandler
|
||||||
alias FarmbotCore.{Asset, BotState, Leds}
|
alias FarmbotCore.{Asset, BotState, Leds}
|
||||||
|
|
||||||
|
import ExUnit.CaptureLog
|
||||||
|
|
||||||
def auto_sync_off, do: expect(Asset.Query, :auto_sync?, fn -> false end)
|
def auto_sync_off, do: expect(Asset.Query, :auto_sync?, fn -> false end)
|
||||||
|
def auto_sync_on, do: expect(Asset.Query, :auto_sync?, fn -> true end)
|
||||||
|
|
||||||
def expect_sync_status_to_be(status),
|
def expect_sync_status_to_be(status),
|
||||||
do: expect(BotState, :set_sync_status, fn ^status -> :ok end)
|
do: expect(BotState, :set_sync_status, fn ^status -> :ok end)
|
||||||
|
@ -22,4 +26,41 @@ defmodule AutoSyncAssetHandlerTest do
|
||||||
expect_green_leds(:slow_blink)
|
expect_green_leds(:slow_blink)
|
||||||
AutoSyncAssetHandler.handle_asset("Point", 23, nil)
|
AutoSyncAssetHandler.handle_asset("Point", 23, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "Handles @no_cache_kinds" do
|
||||||
|
id = 64
|
||||||
|
params = %{}
|
||||||
|
|
||||||
|
kind =
|
||||||
|
~w(Device FbosConfig FirmwareConfig FarmwareEnv FarmwareInstallation)
|
||||||
|
|> Enum.shuffle()
|
||||||
|
|> Enum.at(0)
|
||||||
|
|
||||||
|
expect(Asset.Command, :update, 1, fn ^kind, ^id, ^params -> :ok end)
|
||||||
|
assert :ok = AutoSyncAssetHandler.cache_sync(kind, id, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "handling of deleted assets when auto_sync is enabled" do
|
||||||
|
auto_sync_on()
|
||||||
|
expect_sync_status_to_be("syncing")
|
||||||
|
expect_sync_status_to_be("synced")
|
||||||
|
expect_green_leds(:really_fast_blink)
|
||||||
|
expect_green_leds(:solid)
|
||||||
|
AutoSyncAssetHandler.handle_asset("Point", 32, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cache sync" do
|
||||||
|
id = 64
|
||||||
|
params = %{}
|
||||||
|
kind = "Point"
|
||||||
|
# Helpers.expect_log("Autocaching sync #{kind} #{id} #{inspect(params)}")
|
||||||
|
changeset = %{ab: :cd}
|
||||||
|
changesetfaker = fn ^kind, ^id, ^params -> changeset end
|
||||||
|
expect(FarmbotCore.Asset.Command, :new_changeset, 1, changesetfaker)
|
||||||
|
expect(FarmbotExt.API.EagerLoader, :cache, 1, fn ^changeset -> :ok end)
|
||||||
|
expect_sync_status_to_be("sync_now")
|
||||||
|
expect_green_leds(:slow_blink)
|
||||||
|
do_it = fn -> AutoSyncAssetHandler.cache_sync(kind, id, params) end
|
||||||
|
assert capture_log(do_it) =~ "Autocaching sync Point 64 %{}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule AutoSyncChannelTest do
|
defmodule AutoSyncChannelTest do
|
||||||
require Helpers
|
require Helpers
|
||||||
use ExUnit.Case, async: true
|
use ExUnit.Case, async: false
|
||||||
use Mimic
|
use Mimic
|
||||||
alias FarmbotExt.AMQP.AutoSyncChannel
|
alias FarmbotExt.AMQP.AutoSyncChannel
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ defmodule AutoSyncChannelTest do
|
||||||
expect(Preloader, :preload_all, 1, fn -> :ok end)
|
expect(Preloader, :preload_all, 1, fn -> :ok end)
|
||||||
pid = generate_pid()
|
pid = generate_pid()
|
||||||
send(pid, msg)
|
send(pid, msg)
|
||||||
Process.sleep(5)
|
Helpers.wait_for(pid)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "basic_cancel", do: ensure_response_to({:basic_cancel, :anything})
|
test "basic_cancel", do: ensure_response_to({:basic_cancel, :anything})
|
||||||
|
@ -78,6 +78,7 @@ defmodule AutoSyncChannelTest do
|
||||||
# Helpers.expect_log("Failed to connect to AutoSync channel: :whatever")
|
# Helpers.expect_log("Failed to connect to AutoSync channel: :whatever")
|
||||||
# Helpers.expect_log("Disconnected from AutoSync channel: :normal")
|
# Helpers.expect_log("Disconnected from AutoSync channel: :normal")
|
||||||
pid = generate_pid()
|
pid = generate_pid()
|
||||||
|
IO.puts(" =====RICK: This test blinks and you should fix it.")
|
||||||
assert %{chan: nil, conn: nil, preloaded: true} == AutoSyncChannel.network_status(pid)
|
assert %{chan: nil, conn: nil, preloaded: true} == AutoSyncChannel.network_status(pid)
|
||||||
GenServer.stop(pid, :normal)
|
GenServer.stop(pid, :normal)
|
||||||
end
|
end
|
||||||
|
@ -153,6 +154,7 @@ defmodule AutoSyncChannelTest do
|
||||||
:ok
|
:ok
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
Helpers.wait_for(pid)
|
||||||
Process.sleep(1000)
|
Process.sleep(1000)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
defmodule FarmbotExt.AMQP.BotStateChannelTest do
|
defmodule FarmbotExt.AMQP.BotStateChannelTest do
|
||||||
use ExUnit.Case
|
use ExUnit.Case, async: false
|
||||||
use Mimic
|
use Mimic
|
||||||
|
|
||||||
# alias FarmbotExt.AMQP.BotStateChannel
|
# alias FarmbotExt.AMQP.BotStateChannel
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
defmodule FarmbotExt.API.ImageUploaderTest do
|
||||||
|
require Helpers
|
||||||
|
use ExUnit.Case, async: false
|
||||||
|
use Mimic
|
||||||
|
alias FarmbotExt.API.ImageUploader
|
||||||
|
setup :verify_on_exit!
|
||||||
|
setup :set_mimic_global
|
||||||
|
|
||||||
|
test "force checkup" do
|
||||||
|
pid =
|
||||||
|
if Process.whereis(ImageUploader) do
|
||||||
|
Process.whereis(ImageUploader)
|
||||||
|
else
|
||||||
|
{:ok, p} = ImageUploader.start_link([])
|
||||||
|
p
|
||||||
|
end
|
||||||
|
|
||||||
|
["a.jpg", "b.jpeg", "c.png", "d.gif"]
|
||||||
|
|> Enum.map(fn fname ->
|
||||||
|
f = "/tmp/images/#{fname}"
|
||||||
|
File.touch!(f)
|
||||||
|
File.write(f, "X")
|
||||||
|
end)
|
||||||
|
|
||||||
|
expect(FarmbotExt.API, :upload_image, 4, fn
|
||||||
|
"/tmp/images/d.gif", _meta -> {:error, %{status: 401, body: %{}}}
|
||||||
|
_image_filename, _meta -> {:ok, %{status: 201, body: %{}}}
|
||||||
|
end)
|
||||||
|
|
||||||
|
err_msg =
|
||||||
|
"Upload Error (/tmp/images/d.gif): " <>
|
||||||
|
"{:error, %{body: %{}, status: 401}}"
|
||||||
|
|
||||||
|
Helpers.expect_log("Uploaded image: /tmp/images/a.jpg")
|
||||||
|
Helpers.expect_log("Uploaded image: /tmp/images/b.jpeg")
|
||||||
|
Helpers.expect_log("Uploaded image: /tmp/images/c.png")
|
||||||
|
Helpers.expect_log(err_msg)
|
||||||
|
|
||||||
|
ImageUploader.force_checkup()
|
||||||
|
send(pid, :timeout)
|
||||||
|
Helpers.wait_for(pid)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,28 +1,54 @@
|
||||||
Application.ensure_all_started(:farmbot)
|
Application.ensure_all_started(:farmbot)
|
||||||
|
|
||||||
Mimic.copy(AMQP.Channel)
|
[
|
||||||
Mimic.copy(FarmbotCeleryScript.SysCalls.Stubs)
|
AMQP.Channel,
|
||||||
Mimic.copy(FarmbotCore.Asset.Command)
|
FarmbotCeleryScript.SysCalls.Stubs,
|
||||||
Mimic.copy(FarmbotCore.Asset.Query)
|
FarmbotCore.Asset.Command,
|
||||||
Mimic.copy(FarmbotCore.BotState)
|
FarmbotCore.Asset.Query,
|
||||||
Mimic.copy(FarmbotCore.Leds)
|
FarmbotCore.BotState,
|
||||||
Mimic.copy(FarmbotCore.LogExecutor)
|
FarmbotCore.Leds,
|
||||||
Mimic.copy(FarmbotExt.AMQP.ConnectionWorker)
|
FarmbotCore.LogExecutor,
|
||||||
Mimic.copy(FarmbotExt.API.EagerLoader.Supervisor)
|
FarmbotExt.AMQP.AutoSyncAssetHandler,
|
||||||
Mimic.copy(FarmbotExt.API.Preloader)
|
FarmbotExt.AMQP.ConnectionWorker,
|
||||||
Mimic.copy(FarmbotExt.API)
|
FarmbotExt.API,
|
||||||
Mimic.copy(FarmbotExt.AMQP.AutoSyncAssetHandler)
|
FarmbotExt.API.EagerLoader,
|
||||||
|
FarmbotExt.API.EagerLoader.Supervisor,
|
||||||
|
FarmbotExt.API.Preloader
|
||||||
|
]
|
||||||
|
|> Enum.map(&Mimic.copy/1)
|
||||||
|
|
||||||
timeout = System.get_env("EXUNIT_TIMEOUT")
|
timeout = System.get_env("EXUNIT_TIMEOUT") || "5000"
|
||||||
System.put_env("LOG_SILENCE", "true")
|
System.put_env("LOG_SILENCE", "true")
|
||||||
|
|
||||||
if timeout do
|
ExUnit.start(assert_receive_timeout: String.to_integer(timeout))
|
||||||
ExUnit.start(assert_receive_timeout: String.to_integer(timeout))
|
|
||||||
else
|
|
||||||
ExUnit.start()
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule Helpers do
|
defmodule Helpers do
|
||||||
|
# Maybe I don't need this?
|
||||||
|
# Maybe I could use `start_supervised`?
|
||||||
|
# https://hexdocs.pm/ex_unit/ExUnit.Callbacks.html#start_supervised/2
|
||||||
|
|
||||||
|
@wait_time 180
|
||||||
|
# Base case: We have a pid
|
||||||
|
def wait_for(pid) when is_pid(pid), do: check_on_mbox(pid)
|
||||||
|
# Failure case: We failed to find a pid for a module.
|
||||||
|
def wait_for(nil), do: raise("Attempted to wait on bad module/pid")
|
||||||
|
# Edge case: We have a module and need to try finding its pid.
|
||||||
|
def wait_for(mod), do: wait_for(Process.whereis(mod))
|
||||||
|
|
||||||
|
# Enter recursive loop
|
||||||
|
defp check_on_mbox(pid) do
|
||||||
|
Process.sleep(@wait_time)
|
||||||
|
wait(pid, Process.info(pid, :message_queue_len))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Exit recursive loop (mbox is clear)
|
||||||
|
defp wait(_, {:message_queue_len, 0}), do: Process.sleep(@wait_time * 3)
|
||||||
|
# Exit recursive loop (pid is dead)
|
||||||
|
defp wait(_, nil), do: Process.sleep(@wait_time * 3)
|
||||||
|
|
||||||
|
# Continue recursive loop
|
||||||
|
defp wait(pid, {:message_queue_len, _n}), do: check_on_mbox(pid)
|
||||||
|
|
||||||
defmacro expect_log(message) do
|
defmacro expect_log(message) do
|
||||||
quote do
|
quote do
|
||||||
expect(FarmbotCore.LogExecutor, :execute, fn log ->
|
expect(FarmbotCore.LogExecutor, :execute, fn log ->
|
||||||
|
|
|
@ -78,28 +78,6 @@ defmodule FarmbotFirmware do
|
||||||
|
|
||||||
and reply with `:ok | {:error, term()}`
|
and reply with `:ok | {:error, term()}`
|
||||||
|
|
||||||
# VCR
|
|
||||||
|
|
||||||
This server can save all the input and output gcodes to a text file for
|
|
||||||
further external analysis or playback later.
|
|
||||||
|
|
||||||
## Using VCR mode
|
|
||||||
|
|
||||||
The server can be started in VCR mode by doing:
|
|
||||||
|
|
||||||
FarmbotFirmware.start_link([transport: FarmbotFirmware.StubTransport, vcr_path: "/tmp/vcr.txt"], [])
|
|
||||||
|
|
||||||
or can be started at runtime:
|
|
||||||
|
|
||||||
FarmbotFirmware.enter_vcr_mode(firmware_server, "/tmp/vcr.txt")
|
|
||||||
|
|
||||||
in either case the VCR recording needs to be stopped:
|
|
||||||
|
|
||||||
FarmbotFirmware.exit_vcr_mode(firmware_server)
|
|
||||||
|
|
||||||
VCRs can later be played back:
|
|
||||||
|
|
||||||
FarmbotFirmware.VCR.playback!("/tmp/vcr.txt")
|
|
||||||
"""
|
"""
|
||||||
use GenServer
|
use GenServer
|
||||||
require Logger
|
require Logger
|
||||||
|
@ -129,7 +107,6 @@ defmodule FarmbotFirmware do
|
||||||
:command_queue,
|
:command_queue,
|
||||||
:caller_pid,
|
:caller_pid,
|
||||||
:current,
|
:current,
|
||||||
:vcr_fd,
|
|
||||||
:reset,
|
:reset,
|
||||||
:reset_pid
|
:reset_pid
|
||||||
]
|
]
|
||||||
|
@ -146,7 +123,6 @@ defmodule FarmbotFirmware do
|
||||||
command_queue: [{pid(), GCODE.t()}],
|
command_queue: [{pid(), GCODE.t()}],
|
||||||
caller_pid: nil | pid,
|
caller_pid: nil | pid,
|
||||||
current: nil | GCODE.t(),
|
current: nil | GCODE.t(),
|
||||||
vcr_fd: nil | File.io_device(),
|
|
||||||
reset: module(),
|
reset: module(),
|
||||||
reset_pid: nil | pid()
|
reset_pid: nil | pid()
|
||||||
}
|
}
|
||||||
|
@ -209,22 +185,6 @@ defmodule FarmbotFirmware do
|
||||||
GenServer.call(server, :reset)
|
GenServer.call(server, :reset)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
|
||||||
Sets the Firmware server to record input and output GCODES
|
|
||||||
to a pair of text files.
|
|
||||||
"""
|
|
||||||
def enter_vcr_mode(server \\ __MODULE__, tape_path) do
|
|
||||||
GenServer.call(server, {:enter_vcr_mode, tape_path})
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Sets the Firmware server to stop recording input and output
|
|
||||||
GCODES.
|
|
||||||
"""
|
|
||||||
def exit_vcr_mode(server \\ __MODULE__) do
|
|
||||||
GenServer.cast(server, :exit_vcr_mode)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Starting the Firmware server requires at least:
|
Starting the Firmware server requires at least:
|
||||||
* `:transport` - a module implementing the Transport GenServer behaviour.
|
* `:transport` - a module implementing the Transport GenServer behaviour.
|
||||||
|
@ -252,18 +212,6 @@ defmodule FarmbotFirmware do
|
||||||
# probably?
|
# probably?
|
||||||
reset = Keyword.get(args, :reset) || FarmbotFirmware.NullReset
|
reset = Keyword.get(args, :reset) || FarmbotFirmware.NullReset
|
||||||
|
|
||||||
vcr_fd =
|
|
||||||
case Keyword.get(args, :vcr_path) do
|
|
||||||
nil ->
|
|
||||||
nil
|
|
||||||
|
|
||||||
tape_path ->
|
|
||||||
{:ok, vcr_fd} =
|
|
||||||
File.open(tape_path, [:binary, :append, :exclusive, :write])
|
|
||||||
|
|
||||||
vcr_fd
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add an anon function that transport implementations should call.
|
# Add an anon function that transport implementations should call.
|
||||||
fw = self()
|
fw = self()
|
||||||
fun = fn {_, _} = code -> GenServer.cast(fw, code) end
|
fun = fn {_, _} = code -> GenServer.cast(fw, code) end
|
||||||
|
@ -279,11 +227,10 @@ defmodule FarmbotFirmware do
|
||||||
reset: reset,
|
reset: reset,
|
||||||
reset_pid: nil,
|
reset_pid: nil,
|
||||||
command_queue: [],
|
command_queue: [],
|
||||||
configuration_queue: [],
|
configuration_queue: []
|
||||||
vcr_fd: vcr_fd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
send(self(), :timeout)
|
send_timeout_self()
|
||||||
{:ok, state}
|
{:ok, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -347,7 +294,7 @@ defmodule FarmbotFirmware do
|
||||||
]
|
]
|
||||||
} = state
|
} = state
|
||||||
) do
|
) do
|
||||||
case GenServer.call(state.transport_pid, {tag, code}) do
|
case call_transport(state.transport_pid, {tag, code}, 297) do
|
||||||
:ok ->
|
:ok ->
|
||||||
new_state = %{
|
new_state = %{
|
||||||
state
|
state
|
||||||
|
@ -358,7 +305,6 @@ defmodule FarmbotFirmware do
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = side_effects(new_state, :handle_output_gcode, [{state.tag, code}])
|
_ = side_effects(new_state, :handle_output_gcode, [{state.tag, code}])
|
||||||
_ = vcr_write(state, :out, {state.tag, code})
|
|
||||||
|
|
||||||
{:noreply, new_state}
|
{:noreply, new_state}
|
||||||
|
|
||||||
|
@ -370,11 +316,10 @@ defmodule FarmbotFirmware do
|
||||||
def handle_info(:timeout, %{configuration_queue: [code | rest]} = state) do
|
def handle_info(:timeout, %{configuration_queue: [code | rest]} = state) do
|
||||||
# Logger.debug("Starting next configuration code: #{inspect(code)}")
|
# Logger.debug("Starting next configuration code: #{inspect(code)}")
|
||||||
|
|
||||||
case GenServer.call(state.transport_pid, {state.tag, code}) do
|
case call_transport(state.transport_pid, {state.tag, code}, 319) do
|
||||||
:ok ->
|
:ok ->
|
||||||
new_state = %{state | current: code, configuration_queue: rest}
|
new_state = %{state | current: code, configuration_queue: rest}
|
||||||
_ = side_effects(new_state, :handle_output_gcode, [{state.tag, code}])
|
_ = side_effects(new_state, :handle_output_gcode, [{state.tag, code}])
|
||||||
_ = vcr_write(state, :out, {state.tag, code})
|
|
||||||
{:noreply, new_state}
|
{:noreply, new_state}
|
||||||
|
|
||||||
error ->
|
error ->
|
||||||
|
@ -389,7 +334,6 @@ defmodule FarmbotFirmware do
|
||||||
for {pid, _code} <- state.command_queue,
|
for {pid, _code} <- state.command_queue,
|
||||||
do: send(pid, {state.tag, {:report_busy, []}})
|
do: send(pid, {state.tag, {:report_busy, []}})
|
||||||
|
|
||||||
# Logger.debug "Got checkup message when current command still executing"
|
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -400,7 +344,7 @@ defmodule FarmbotFirmware do
|
||||||
for {pid, _code} <- state.command_queue,
|
for {pid, _code} <- state.command_queue,
|
||||||
do: send(pid, {state.tag, {:report_busy, []}})
|
do: send(pid, {state.tag, {:report_busy, []}})
|
||||||
|
|
||||||
case GenServer.call(state.transport_pid, {tag, code}) do
|
case call_transport(state.transport_pid, {tag, code}, 348) do
|
||||||
:ok ->
|
:ok ->
|
||||||
new_state = %{
|
new_state = %{
|
||||||
state
|
state
|
||||||
|
@ -411,7 +355,6 @@ defmodule FarmbotFirmware do
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = side_effects(new_state, :handle_output_gcode, [{state.tag, code}])
|
_ = side_effects(new_state, :handle_output_gcode, [{state.tag, code}])
|
||||||
_ = vcr_write(state, :out, {state.tag, code})
|
|
||||||
for {pid, _code} <- rest, do: send(pid, {state.tag, {:report_busy, []}})
|
for {pid, _code} <- rest, do: send(pid, {state.tag, {:report_busy, []}})
|
||||||
|
|
||||||
{:noreply, new_state}
|
{:noreply, new_state}
|
||||||
|
@ -438,7 +381,12 @@ defmodule FarmbotFirmware do
|
||||||
true = Process.demonitor(state.transport_ref)
|
true = Process.demonitor(state.transport_ref)
|
||||||
end
|
end
|
||||||
|
|
||||||
:ok = GenServer.stop(state.transport_pid, :normal)
|
if is_pid(state.transport_pid) do
|
||||||
|
Logger.debug("closing transport")
|
||||||
|
:ok = GenServer.stop(state.transport_pid, :normal)
|
||||||
|
else
|
||||||
|
Logger.debug("No tranport pid found. Nothing to close")
|
||||||
|
end
|
||||||
|
|
||||||
next_state =
|
next_state =
|
||||||
goto(
|
goto(
|
||||||
|
@ -473,7 +421,7 @@ defmodule FarmbotFirmware do
|
||||||
|
|
||||||
next_state = %{state | transport: module, transport_args: transport_args}
|
next_state = %{state | transport: module, transport_args: transport_args}
|
||||||
|
|
||||||
send(self(), :timeout)
|
send_timeout_self()
|
||||||
{:reply, :ok, next_state}
|
{:reply, :ok, next_state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -485,16 +433,6 @@ defmodule FarmbotFirmware do
|
||||||
{:reply, {:error, s}, state}
|
{:reply, {:error, s}, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_call({:enter_vcr_mode, tape_path}, _from, state) do
|
|
||||||
with {:ok, vcr_fd} <-
|
|
||||||
File.open(tape_path, [:binary, :append, :exclusive, :write]) do
|
|
||||||
{:reply, :ok, %{state | vcr_fd: vcr_fd}}
|
|
||||||
else
|
|
||||||
error ->
|
|
||||||
{:reply, error, state}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_call({tag, {kind, args}}, from, state) do
|
def handle_call({tag, {kind, args}}, from, state) do
|
||||||
handle_command({tag, {kind, args}}, from, state)
|
handle_command({tag, {kind, args}}, from, state)
|
||||||
end
|
end
|
||||||
|
@ -521,7 +459,7 @@ defmodule FarmbotFirmware do
|
||||||
for {pid, _code} <- state.command_queue,
|
for {pid, _code} <- state.command_queue,
|
||||||
do: send(pid, {state.tag, {:report_emergency_lock, []}})
|
do: send(pid, {state.tag, {:report_emergency_lock, []}})
|
||||||
|
|
||||||
send(self(), :timeout)
|
send_timeout_self()
|
||||||
|
|
||||||
{:reply, {:ok, tag},
|
{:reply, {:ok, tag},
|
||||||
%{state | command_queue: [{pid, code}], configuration_queue: []}}
|
%{state | command_queue: [{pid, code}], configuration_queue: []}}
|
||||||
|
@ -533,7 +471,7 @@ defmodule FarmbotFirmware do
|
||||||
{pid, _ref},
|
{pid, _ref},
|
||||||
state
|
state
|
||||||
) do
|
) do
|
||||||
send(self(), :timeout)
|
send_timeout_self()
|
||||||
|
|
||||||
{:reply, {:ok, tag},
|
{:reply, {:ok, tag},
|
||||||
%{state | command_queue: [{pid, code}], configuration_queue: []}}
|
%{state | command_queue: [{pid, code}], configuration_queue: []}}
|
||||||
|
@ -541,7 +479,7 @@ defmodule FarmbotFirmware do
|
||||||
|
|
||||||
# If not in an acceptable state, return an error immediately.
|
# If not in an acceptable state, return an error immediately.
|
||||||
def handle_command(_, _, %{status: s} = state)
|
def handle_command(_, _, %{status: s} = state)
|
||||||
when s in [:transport_boot, :boot, :no_config, :configuration] do
|
when s in [:boot, :no_config] do
|
||||||
{:reply, {:error, "Can't send command when in #{inspect(s)} state"}, state}
|
{:reply, {:error, "Can't send command when in #{inspect(s)} state"}, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -550,14 +488,14 @@ defmodule FarmbotFirmware do
|
||||||
|
|
||||||
case {new_state.status, state.current} do
|
case {new_state.status, state.current} do
|
||||||
{:idle, nil} ->
|
{:idle, nil} ->
|
||||||
send(self(), :timeout)
|
send_timeout_self()
|
||||||
{:reply, {:ok, tag}, new_state}
|
{:reply, {:ok, tag}, new_state}
|
||||||
|
|
||||||
# Don't do any flow control if state is emergency_lock.
|
# Don't do any flow control if state is emergency_lock.
|
||||||
# This allows a transport to decide
|
# This allows a transport to decide
|
||||||
# if a command should be blocked or not.
|
# if a command should be blocked or not.
|
||||||
{:emergency_lock, _} ->
|
{:emergency_lock, _} ->
|
||||||
send(self(), :timeout)
|
send_timeout_self()
|
||||||
{:reply, {:ok, tag}, new_state}
|
{:reply, {:ok, tag}, new_state}
|
||||||
|
|
||||||
_unknown ->
|
_unknown ->
|
||||||
|
@ -565,15 +503,9 @@ defmodule FarmbotFirmware do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_cast(:exit_vcr_mode, state) do
|
|
||||||
state.vcr_fd && File.close(state.vcr_fd)
|
|
||||||
{:noreply, %{state | vcr_fd: nil}}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Extracts tag
|
# Extracts tag
|
||||||
def handle_cast({tag, {_, _} = code}, state) do
|
def handle_cast({tag, {_, _} = code}, state) do
|
||||||
_ = side_effects(state, :handle_input_gcode, [{tag, code}])
|
_ = side_effects(state, :handle_input_gcode, [{tag, code}])
|
||||||
_ = vcr_write(state, :in, {tag, code})
|
|
||||||
handle_report(code, %{state | tag: tag})
|
handle_report(code, %{state | tag: tag})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -585,7 +517,7 @@ defmodule FarmbotFirmware do
|
||||||
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
if state.caller_pid, do: send(state.caller_pid, {state.tag, code})
|
||||||
for {pid, _code} <- state.command_queue, do: send(pid, code)
|
for {pid, _code} <- state.command_queue, do: send(pid, code)
|
||||||
|
|
||||||
send(self(), :timeout)
|
send_timeout_self()
|
||||||
{:noreply, goto(%{state | current: nil, caller_pid: nil}, :emergency_lock)}
|
{:noreply, goto(%{state | current: nil, caller_pid: nil}, :emergency_lock)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -598,10 +530,7 @@ defmodule FarmbotFirmware do
|
||||||
handle_report({:report_no_config, []}, state)
|
handle_report({:report_no_config, []}, state)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_report(
|
def handle_report({:report_idle, []}, %{status: :boot} = state) do
|
||||||
{:report_idle, []},
|
|
||||||
%{status: :boot} = state
|
|
||||||
) do
|
|
||||||
Logger.info("ARDUINO STARTUP COMPLETE (idle) transport=#{state.transport}")
|
Logger.info("ARDUINO STARTUP COMPLETE (idle) transport=#{state.transport}")
|
||||||
handle_report({:report_no_config, []}, state)
|
handle_report({:report_no_config, []}, state)
|
||||||
end
|
end
|
||||||
|
@ -648,7 +577,7 @@ defmodule FarmbotFirmware do
|
||||||
do: to_process ++ [{:command_movement_find_home, [:x]}],
|
do: to_process ++ [{:command_movement_find_home, [:x]}],
|
||||||
else: to_process
|
else: to_process
|
||||||
|
|
||||||
send(self(), :timeout)
|
send_timeout_self()
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
goto(%{state | tag: tag, configuration_queue: to_process}, :configuration)}
|
goto(%{state | tag: tag, configuration_queue: to_process}, :configuration)}
|
||||||
|
@ -684,7 +613,7 @@ defmodule FarmbotFirmware do
|
||||||
|
|
||||||
side_effects(state, :handle_busy, [false])
|
side_effects(state, :handle_busy, [false])
|
||||||
side_effects(state, :handle_idle, [true])
|
side_effects(state, :handle_idle, [true])
|
||||||
send(self(), :timeout)
|
send_timeout_self()
|
||||||
{:noreply, goto(%{state | caller_pid: nil, current: nil}, :idle)}
|
{:noreply, goto(%{state | caller_pid: nil, current: nil}, :idle)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -705,7 +634,7 @@ defmodule FarmbotFirmware do
|
||||||
|
|
||||||
new_state = %{state | current: nil, caller_pid: nil}
|
new_state = %{state | current: nil, caller_pid: nil}
|
||||||
side_effects(state, :handle_busy, [false])
|
side_effects(state, :handle_busy, [false])
|
||||||
send(self(), :timeout)
|
send_timeout_self()
|
||||||
{:noreply, goto(new_state, :idle)}
|
{:noreply, goto(new_state, :idle)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -739,7 +668,7 @@ defmodule FarmbotFirmware do
|
||||||
do: send(pid, {state.tag, {:report_busy, []}})
|
do: send(pid, {state.tag, {:report_busy, []}})
|
||||||
|
|
||||||
side_effects(state, :handle_busy, [false])
|
side_effects(state, :handle_busy, [false])
|
||||||
send(self(), :timeout)
|
send_timeout_self()
|
||||||
{:noreply, %{state | caller_pid: nil, current: nil}}
|
{:noreply, %{state | caller_pid: nil, current: nil}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -761,7 +690,7 @@ defmodule FarmbotFirmware do
|
||||||
for {pid, _code} <- state.command_queue,
|
for {pid, _code} <- state.command_queue,
|
||||||
do: send(pid, {state.tag, {:report_busy, []}})
|
do: send(pid, {state.tag, {:report_busy, []}})
|
||||||
|
|
||||||
send(self(), :timeout)
|
send_timeout_self()
|
||||||
{:noreply, %{state | caller_pid: nil, current: nil}}
|
{:noreply, %{state | caller_pid: nil, current: nil}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -796,7 +725,7 @@ defmodule FarmbotFirmware do
|
||||||
to_process = [{:parameter_write, param}]
|
to_process = [{:parameter_write, param}]
|
||||||
side_effects(state, :handle_parameter_value, [param])
|
side_effects(state, :handle_parameter_value, [param])
|
||||||
side_effects(state, :handle_parameter_calibration_value, [param])
|
side_effects(state, :handle_parameter_calibration_value, [param])
|
||||||
send(self(), :timeout)
|
send_timeout_self()
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
goto(
|
goto(
|
||||||
|
@ -996,30 +925,28 @@ defmodule FarmbotFirmware do
|
||||||
defp side_effects(%{side_effects: m}, function, args),
|
defp side_effects(%{side_effects: m}, function, args),
|
||||||
do: apply(m, function, args)
|
do: apply(m, function, args)
|
||||||
|
|
||||||
@spec vcr_write(state, :in | :out, GCODE.t()) :: :ok
|
defp send_timeout_self do
|
||||||
defp vcr_write(%{vcr_fd: nil}, _direction, _code), do: :ok
|
send(self(), :timeout)
|
||||||
|
end
|
||||||
|
|
||||||
defp vcr_write(state, :in, code), do: vcr_write(state, "<", code)
|
defp call_transport(nil, args, where) do
|
||||||
|
msg =
|
||||||
|
"#{inspect(where)} Firmware not ready. A restart may be required if not already started (#{
|
||||||
|
inspect(args)
|
||||||
|
})"
|
||||||
|
|
||||||
defp vcr_write(state, :out, code), do: vcr_write(state, "\n>", code)
|
Logger.debug(msg)
|
||||||
|
{:error, msg}
|
||||||
|
end
|
||||||
|
|
||||||
defp vcr_write(state, direction, code) do
|
defp call_transport(transport_pid, args, where) do
|
||||||
data = GCODE.encode(code)
|
# Returns :ok
|
||||||
time = :os.system_time(:second)
|
response = GenServer.call(transport_pid, args)
|
||||||
|
|
||||||
current_data =
|
unless response == :ok do
|
||||||
if state.current do
|
Logger.debug("#{inspect(where)}: returned #{inspect(response)}")
|
||||||
GCODE.encode({state.tag, state.current})
|
end
|
||||||
else
|
|
||||||
"nil"
|
|
||||||
end
|
|
||||||
|
|
||||||
state_data =
|
response
|
||||||
"#{state.status} | #{current_data} | #{inspect(state.caller_pid)}"
|
|
||||||
|
|
||||||
IO.write(
|
|
||||||
state.vcr_fd,
|
|
||||||
direction <> " #{time} " <> data <> " state=" <> state_data <> "\n"
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule FarmbotFirmware.CommandTest do
|
||||||
import ExUnit.CaptureLog
|
import ExUnit.CaptureLog
|
||||||
@subject FarmbotFirmware.Command
|
@subject FarmbotFirmware.Command
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "command() runs RPCs" do
|
test "command() runs RPCs" do
|
||||||
arg = [transport: FarmbotFirmware.StubTransport]
|
arg = [transport: FarmbotFirmware.StubTransport]
|
||||||
{:ok, pid} = FarmbotFirmware.start_link(arg, [])
|
{:ok, pid} = FarmbotFirmware.start_link(arg, [])
|
||||||
|
@ -19,6 +20,7 @@ defmodule FarmbotFirmware.CommandTest do
|
||||||
assert :ok == FarmbotFirmware.command(pid, cmd)
|
assert :ok == FarmbotFirmware.command(pid, cmd)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "command() refuses to run RPCs in :boot state" do
|
test "command() refuses to run RPCs in :boot state" do
|
||||||
arg = [transport: FarmbotFirmware.StubTransport]
|
arg = [transport: FarmbotFirmware.StubTransport]
|
||||||
{:ok, pid} = FarmbotFirmware.start_link(arg, [])
|
{:ok, pid} = FarmbotFirmware.start_link(arg, [])
|
||||||
|
|
|
@ -4,6 +4,7 @@ defmodule FarmbotFirmware.UARTTransportTest do
|
||||||
doctest FarmbotFirmware.UARTTransport
|
doctest FarmbotFirmware.UARTTransport
|
||||||
alias FarmbotFirmware.{UartDefaultAdapter, UARTTransport}
|
alias FarmbotFirmware.{UartDefaultAdapter, UARTTransport}
|
||||||
setup :verify_on_exit!
|
setup :verify_on_exit!
|
||||||
|
import ExUnit.CaptureLog
|
||||||
|
|
||||||
test "UARTTransport.init/1" do
|
test "UARTTransport.init/1" do
|
||||||
expect(UartDefaultAdapter, :start_link, fn ->
|
expect(UartDefaultAdapter, :start_link, fn ->
|
||||||
|
@ -61,15 +62,22 @@ defmodule FarmbotFirmware.UARTTransportTest do
|
||||||
fake_opts
|
fake_opts
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
error = "Simulated UART failure. This is OK"
|
||||||
|
|
||||||
expect(UartDefaultAdapter, :open, fn _, _, _ ->
|
expect(UartDefaultAdapter, :open, fn _, _, _ ->
|
||||||
{:error, "Simulated UART failure. This is OK"}
|
{:error, error}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
{:noreply, state2, retry_timeout} =
|
logs =
|
||||||
UARTTransport.handle_info(:timeout, state)
|
capture_log(fn ->
|
||||||
|
{:noreply, state2, retry_timeout} =
|
||||||
|
UARTTransport.handle_info(:timeout, state)
|
||||||
|
|
||||||
assert retry_timeout == 5000
|
assert retry_timeout == 5000
|
||||||
assert state.open == state2.open
|
assert state.open == state2.open
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert logs =~ error
|
||||||
end
|
end
|
||||||
|
|
||||||
test "UARTTransport handles `Circuits-UART` speecific errors" do
|
test "UARTTransport handles `Circuits-UART` speecific errors" do
|
||||||
|
|
|
@ -19,6 +19,7 @@ defmodule FarmbotFirmwareTest do
|
||||||
pid
|
pid
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "various reports" do
|
test "various reports" do
|
||||||
pid = firmware_server()
|
pid = firmware_server()
|
||||||
|
|
||||||
|
@ -63,6 +64,7 @@ defmodule FarmbotFirmwareTest do
|
||||||
Process.sleep(1000)
|
Process.sleep(1000)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "various command()s" do
|
test "various command()s" do
|
||||||
pid = firmware_server()
|
pid = firmware_server()
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,9 @@ defmodule FarmbotFirmware.PackageUtilsTest do
|
||||||
{:ok, path} = PackageUtils.find_hex_file("express_k10")
|
{:ok, path} = PackageUtils.find_hex_file("express_k10")
|
||||||
assert String.contains?(path, "/farmbot_firmware/priv/express_k10.hex")
|
assert String.contains?(path, "/farmbot_firmware/priv/express_k10.hex")
|
||||||
|
|
||||||
|
{:ok, path} = PackageUtils.find_hex_file("none")
|
||||||
|
assert path =~ "lib/farmbot_firmware/priv/eeprom_clear.ino.hex"
|
||||||
|
|
||||||
assert {:error, "unknown firmware hardware: no"} ==
|
assert {:error, "unknown firmware hardware: no"} ==
|
||||||
PackageUtils.find_hex_file("no")
|
PackageUtils.find_hex_file("no")
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
defmodule FarmbotFirmware.ParamTest do
|
defmodule FarmbotFirmware.ParamTest do
|
||||||
use ExUnit.Case
|
use ExUnit.Case
|
||||||
alias FarmbotFirmware.Param
|
alias FarmbotFirmware.Param
|
||||||
|
import ExUnit.CaptureLog
|
||||||
|
|
||||||
|
def t(p, v, expected) do
|
||||||
|
assert Param.to_human(p, v) == expected
|
||||||
|
end
|
||||||
|
|
||||||
test "to_human()" do
|
test "to_human()" do
|
||||||
float_value = 1.23
|
float_value = 1.23
|
||||||
|
@ -9,212 +14,354 @@ defmodule FarmbotFirmware.ParamTest do
|
||||||
steps_per_s = "(steps/s)"
|
steps_per_s = "(steps/s)"
|
||||||
steps_per_mm = "(steps/mm)"
|
steps_per_mm = "(steps/mm)"
|
||||||
|
|
||||||
assert Param.to_human(:param_test, 1) ==
|
t(:pin_guard_5_time_out, 12, {"pin guard 5 timeout", "(seconds)", "12"})
|
||||||
{"param_test", nil, true}
|
t(:pin_guard_5_pin_nr, 12, {"pin guard 5 pin number", nil, "12"})
|
||||||
|
t(:pin_guard_5_active_state, 0, {"pin guard 5 safe state", nil, "HIGH"})
|
||||||
assert Param.to_human(:param_config_ok, 1) ==
|
t(:pin_guard_4_time_out, 12, {"pin guard 4 timeout", "(seconds)", "12"})
|
||||||
{"param_config_ok", nil, true}
|
t(:pin_guard_4_pin_nr, 12, {"pin guard 4 pin number", nil, "12"})
|
||||||
|
t(:pin_guard_4_active_state, 0, {"pin guard 4 safe state", nil, "HIGH"})
|
||||||
assert Param.to_human(:param_use_eeprom, 1) ==
|
t(:pin_guard_3_time_out, 1.0, {"pin guard 3 timeout", "(seconds)", "1"})
|
||||||
{"use eeprom", nil, true}
|
t(:pin_guard_3_pin_nr, 1.0, {"pin guard 3 pin number", nil, "1"})
|
||||||
|
t(:pin_guard_3_active_state, 0, {"pin guard 3 safe state", nil, "HIGH"})
|
||||||
assert Param.to_human(:param_e_stop_on_mov_err, 1) ==
|
t(:pin_guard_2_time_out, 1.0, {"pin guard 2 timeout", "(seconds)", "1"})
|
||||||
{"e-stop on movement errors", nil, true}
|
t(:pin_guard_2_pin_nr, 1.0, {"pin guard 2 pin number", nil, "1"})
|
||||||
|
t(:pin_guard_2_active_state, 0, {"pin guard 2 safe state", nil, "HIGH"})
|
||||||
assert Param.to_human(:movement_timeout_x, float_value) ==
|
t(:pin_guard_1_time_out, 1.0, {"pin guard 1 timeout", "(seconds)", "1"})
|
||||||
{"timeout after, x-axis", seconds, "1.2"}
|
t(:pin_guard_1_pin_nr, 1.0, {"pin guard 1 pin number", nil, "1"})
|
||||||
|
t(:pin_guard_1_active_state, 0, {"pin guard 1 safe state", nil, "HIGH"})
|
||||||
assert Param.to_human(:movement_timeout_y, float_value) ==
|
t(:param_use_eeprom, 1, {"use eeprom", nil, true})
|
||||||
{"timeout after, y-axis", seconds, "1.2"}
|
t(:param_test, 1, {"param_test", nil, true})
|
||||||
|
t(:param_mov_nr_retry, 1.0, {"max retries", nil, "1"})
|
||||||
assert Param.to_human(:movement_timeout_z, float_value) ==
|
t(:param_e_stop_on_mov_err, 1, {"e-stop on movement errors", nil, true})
|
||||||
{"timeout after, z-axis", seconds, "1.2"}
|
t(:param_config_ok, 1, {"param_config_ok", nil, true})
|
||||||
|
t(:movement_stop_at_max_z, 1, {"stop at max, z-axis", nil, true})
|
||||||
assert Param.to_human(:movement_keep_active_x, 1) ==
|
t(:movement_stop_at_max_y, 1, {"stop at max, y-axis", nil, true})
|
||||||
{"always power motors, x-axis", nil, true}
|
t(:movement_stop_at_max_x, 1, {"stop at max, x-axis", nil, true})
|
||||||
|
t(:movement_stop_at_home_z, 1, {"stop at home, z-axis", nil, true})
|
||||||
assert Param.to_human(:movement_keep_active_y, 1) ==
|
t(:movement_stop_at_home_y, 1, {"stop at home, y-axis", nil, true})
|
||||||
{"always power motors, y-axis", nil, true}
|
t(:movement_stop_at_home_x, 1, {"stop at home, x-axis", nil, true})
|
||||||
|
t(:movement_secondary_motor_x, 1, {"enable 2nd x motor", nil, true})
|
||||||
assert Param.to_human(:movement_keep_active_z, 1) ==
|
t(:movement_secondary_motor_invert_x, 1, {"invert 2nd x motor", nil, true})
|
||||||
{"always power motors, z-axis", nil, true}
|
t(:movement_microsteps_z, float_value, {"microsteps, z-axis", nil, "1.2"})
|
||||||
|
t(:movement_microsteps_y, float_value, {"microsteps, y-axis", nil, "1.2"})
|
||||||
assert Param.to_human(:movement_home_at_boot_x, 1) ==
|
t(:movement_microsteps_x, float_value, {"microsteps, x-axis", nil, "1.2"})
|
||||||
{"find home on boot, x-axis", nil, true}
|
t(:movement_keep_active_z, 1, {"always power motors, z-axis", nil, true})
|
||||||
|
t(:movement_keep_active_y, 1, {"always power motors, y-axis", nil, true})
|
||||||
assert Param.to_human(:movement_home_at_boot_y, 1) ==
|
t(:movement_keep_active_x, 1, {"always power motors, x-axis", nil, true})
|
||||||
{"find home on boot, y-axis", nil, true}
|
t(:movement_invert_motor_z, 1, {"invert motor, z-axis", nil, true})
|
||||||
|
t(:movement_invert_motor_y, 1, {"invert motor, y-axis", nil, true})
|
||||||
assert Param.to_human(:movement_home_at_boot_z, 1) ==
|
t(:movement_invert_motor_x, 1, {"invert motor, x-axis", nil, true})
|
||||||
{"find home on boot, z-axis", nil, true}
|
t(:movement_invert_endpoints_z, 1, {"swap endstops, z-axis", nil, true})
|
||||||
|
t(:movement_invert_endpoints_y, 1, {"swap endstops, y-axis", nil, true})
|
||||||
assert Param.to_human(:movement_invert_endpoints_x, 1) ==
|
t(:movement_invert_endpoints_x, 1, {"swap endstops, x-axis", nil, true})
|
||||||
{"swap endstops, x-axis", nil, true}
|
t(:movement_home_at_boot_z, 1, {"find home on boot, z-axis", nil, true})
|
||||||
|
t(:movement_home_at_boot_y, 1, {"find home on boot, y-axis", nil, true})
|
||||||
assert Param.to_human(:movement_invert_endpoints_y, 1) ==
|
t(:movement_home_at_boot_x, 1, {"find home on boot, x-axis", nil, true})
|
||||||
{"swap endstops, y-axis", nil, true}
|
t(:movement_enable_endpoints_z, 1, {"enable endstops, z-axis", nil, true})
|
||||||
|
t(:movement_enable_endpoints_y, 1, {"enable endstops, y-axis", nil, true})
|
||||||
assert Param.to_human(:movement_invert_endpoints_z, 1) ==
|
t(:movement_enable_endpoints_x, 1, {"enable endstops, x-axis", nil, true})
|
||||||
{"swap endstops, z-axis", nil, true}
|
t(:encoder_type_z, 1.2, {"encoder type, z-axis", nil, "1.2"})
|
||||||
|
t(:encoder_type_y, 1.2, {"encoder type, y-axis", nil, "1.2"})
|
||||||
assert Param.to_human(:movement_enable_endpoints_x, 1) ==
|
t(:encoder_type_x, 1.2, {"encoder type, x-axis", nil, "1.2"})
|
||||||
{"enable endstops, x-axis", nil, true}
|
t(:encoder_invert_z, 1, {"invert encoders, z-axis", nil, true})
|
||||||
|
t(:encoder_invert_y, 1, {"invert encoders, y-axis", nil, true})
|
||||||
assert Param.to_human(:movement_enable_endpoints_y, 1) ==
|
t(:encoder_invert_x, 1, {"invert encoders, x-axis", nil, true})
|
||||||
{"enable endstops, y-axis", nil, true}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_enable_endpoints_z, 1) ==
|
:movement_motor_current_x,
|
||||||
{"enable endstops, z-axis", nil, true}
|
float_value,
|
||||||
|
{"motor current, x-axis", "(milliamps)", "1.2"}
|
||||||
assert Param.to_human(:movement_invert_motor_x, 1) ==
|
)
|
||||||
{"invert motor, x-axis", nil, true}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_invert_motor_y, 1) ==
|
:movement_motor_current_y,
|
||||||
{"invert motor, y-axis", nil, true}
|
float_value,
|
||||||
|
{"motor current, y-axis", "(milliamps)", "1.2"}
|
||||||
assert Param.to_human(:movement_invert_motor_z, 1) ==
|
)
|
||||||
{"invert motor, z-axis", nil, true}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_secondary_motor_x, 1) ==
|
:movement_motor_current_z,
|
||||||
{"enable 2nd x motor", nil, true}
|
float_value,
|
||||||
|
{"motor current, z-axis", "(milliamps)", "1.2"}
|
||||||
assert Param.to_human(:movement_secondary_motor_invert_x, 1) ==
|
)
|
||||||
{"invert 2nd x motor", nil, true}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_stop_at_home_x, 1) ==
|
:movement_stall_sensitivity_x,
|
||||||
{"stop at home, x-axis", nil, true}
|
float_value,
|
||||||
|
{"stall sensitivity, x-axis", nil, "1.2"}
|
||||||
assert Param.to_human(:movement_stop_at_home_y, 1) ==
|
)
|
||||||
{"stop at home, y-axis", nil, true}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_stop_at_home_z, 1) ==
|
:movement_stall_sensitivity_y,
|
||||||
{"stop at home, z-axis", nil, true}
|
float_value,
|
||||||
|
{"stall sensitivity, y-axis", nil, "1.2"}
|
||||||
assert Param.to_human(:movement_step_per_mm_x, float_value) ==
|
)
|
||||||
{"steps per mm, x-axis", steps_per_mm, "1.2"}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_step_per_mm_y, float_value) ==
|
:movement_stall_sensitivity_z,
|
||||||
{"steps per mm, y-axis", steps_per_mm, "1.2"}
|
float_value,
|
||||||
|
{"stall sensitivity, z-axis", nil, "1.2"}
|
||||||
assert Param.to_human(:movement_step_per_mm_z, float_value) ==
|
)
|
||||||
{"steps per mm, z-axis", steps_per_mm, "1.2"}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_min_spd_x, float_value) ==
|
:movement_timeout_x,
|
||||||
{"minimum speed, x-axis", steps_per_s, "1.2"}
|
float_value,
|
||||||
|
{"timeout after, x-axis", seconds, "1.2"}
|
||||||
assert Param.to_human(:movement_min_spd_y, float_value) ==
|
)
|
||||||
{"minimum speed, y-axis", steps_per_s, "1.2"}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_min_spd_z, float_value) ==
|
:movement_timeout_y,
|
||||||
{"minimum speed, z-axis", steps_per_s, "1.2"}
|
float_value,
|
||||||
|
{"timeout after, y-axis", seconds, "1.2"}
|
||||||
assert Param.to_human(:movement_home_spd_x, float_value) ==
|
)
|
||||||
{"homing speed, x-axis", steps_per_s, "1.2"}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_home_spd_y, float_value) ==
|
:movement_timeout_z,
|
||||||
{"homing speed, y-axis", steps_per_s, "1.2"}
|
float_value,
|
||||||
|
{"timeout after, z-axis", seconds, "1.2"}
|
||||||
assert Param.to_human(:movement_home_spd_z, float_value) ==
|
)
|
||||||
{"homing speed, z-axis", steps_per_s, "1.2"}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_max_spd_x, float_value) ==
|
:movement_step_per_mm_x,
|
||||||
{"max speed, x-axis", steps_per_s, "1.2"}
|
float_value,
|
||||||
|
{"steps per mm, x-axis", steps_per_mm, "1.2"}
|
||||||
assert Param.to_human(:movement_max_spd_y, float_value) ==
|
)
|
||||||
{"max speed, y-axis", steps_per_s, "1.2"}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_max_spd_z, float_value) ==
|
:movement_step_per_mm_y,
|
||||||
{"max speed, z-axis", steps_per_s, "1.2"}
|
float_value,
|
||||||
|
{"steps per mm, y-axis", steps_per_mm, "1.2"}
|
||||||
assert Param.to_human(:movement_invert_2_endpoints_x, 1) ==
|
)
|
||||||
{"invert endstops, x-axis", nil, true}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_invert_2_endpoints_y, 1) ==
|
:movement_step_per_mm_z,
|
||||||
{"invert endstops, y-axis", nil, true}
|
float_value,
|
||||||
|
{"steps per mm, z-axis", steps_per_mm, "1.2"}
|
||||||
assert Param.to_human(:movement_invert_2_endpoints_z, 1) ==
|
)
|
||||||
{"invert endstops, z-axis", nil, true}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:encoder_enabled_x, 1) ==
|
:movement_min_spd_x,
|
||||||
{"enable encoders / stall detection, x-axis", nil, true}
|
float_value,
|
||||||
|
{"minimum speed, x-axis", steps_per_s, "1.2"}
|
||||||
assert Param.to_human(:encoder_enabled_y, 1) ==
|
)
|
||||||
{"enable encoders / stall detection, y-axis", nil, true}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:encoder_enabled_z, 1) ==
|
:movement_min_spd_y,
|
||||||
{"enable encoders / stall detection, z-axis", nil, true}
|
float_value,
|
||||||
|
{"minimum speed, y-axis", steps_per_s, "1.2"}
|
||||||
assert Param.to_human(:encoder_type_x, float_value) ==
|
)
|
||||||
{"encoder type, x-axis", nil, "1.2"}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:encoder_type_y, float_value) ==
|
:movement_min_spd_z,
|
||||||
{"encoder type, y-axis", nil, "1.2"}
|
float_value,
|
||||||
|
{"minimum speed, z-axis", steps_per_s, "1.2"}
|
||||||
assert Param.to_human(:encoder_type_z, float_value) ==
|
)
|
||||||
{"encoder type, z-axis", nil, "1.2"}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:encoder_scaling_x, float_value) ==
|
:movement_home_spd_x,
|
||||||
{"encoder scaling, x-axis", nil, "1.2"}
|
float_value,
|
||||||
|
{"homing speed, x-axis", steps_per_s, "1.2"}
|
||||||
assert Param.to_human(:encoder_scaling_y, float_value) ==
|
)
|
||||||
{"encoder scaling, y-axis", nil, "1.2"}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:encoder_scaling_z, float_value) ==
|
:movement_home_spd_y,
|
||||||
{"encoder scaling, z-axis", nil, "1.2"}
|
float_value,
|
||||||
|
{"homing speed, y-axis", steps_per_s, "1.2"}
|
||||||
assert Param.to_human(:encoder_missed_steps_decay_x, float_value) ==
|
)
|
||||||
{"missed step decay, x-axis", steps, "1.2"}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:encoder_missed_steps_decay_y, float_value) ==
|
:movement_home_spd_z,
|
||||||
{"missed step decay, y-axis", steps, "1.2"}
|
float_value,
|
||||||
|
{"homing speed, z-axis", steps_per_s, "1.2"}
|
||||||
assert Param.to_human(:encoder_missed_steps_decay_z, float_value) ==
|
)
|
||||||
{"missed step decay, z-axis", steps, "1.2"}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:encoder_use_for_pos_x, 1) ==
|
:movement_max_spd_x,
|
||||||
{"use encoders for positioning, x-axis", nil, true}
|
float_value,
|
||||||
|
{"max speed, x-axis", steps_per_s, "1.2"}
|
||||||
assert Param.to_human(:encoder_use_for_pos_y, 1) ==
|
)
|
||||||
{"use encoders for positioning, y-axis", nil, true}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:encoder_use_for_pos_z, 1) ==
|
:movement_max_spd_y,
|
||||||
{"use encoders for positioning, z-axis", nil, true}
|
float_value,
|
||||||
|
{"max speed, y-axis", steps_per_s, "1.2"}
|
||||||
assert Param.to_human(:encoder_invert_x, 1) ==
|
)
|
||||||
{"invert encoders, x-axis", nil, true}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:encoder_invert_y, 1) ==
|
:movement_max_spd_z,
|
||||||
{"invert encoders, y-axis", nil, true}
|
float_value,
|
||||||
|
{"max speed, z-axis", steps_per_s, "1.2"}
|
||||||
assert Param.to_human(:encoder_invert_z, 1) ==
|
)
|
||||||
{"invert encoders, z-axis", nil, true}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_stop_at_max_x, 1) ==
|
:movement_invert_2_endpoints_x,
|
||||||
{"stop at max, x-axis", nil, true}
|
1,
|
||||||
|
{"invert endstops, x-axis", nil, true}
|
||||||
assert Param.to_human(:movement_stop_at_max_y, 1) ==
|
)
|
||||||
{"stop at max, y-axis", nil, true}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:movement_stop_at_max_z, 1) ==
|
:movement_invert_2_endpoints_y,
|
||||||
{"stop at max, z-axis", nil, true}
|
1,
|
||||||
|
{"invert endstops, y-axis", nil, true}
|
||||||
assert Param.to_human(:pin_guard_1_active_state, 0) ==
|
)
|
||||||
{"pin guard 1 safe state", nil, "HIGH"}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:pin_guard_2_active_state, 0) ==
|
:movement_invert_2_endpoints_z,
|
||||||
{"pin guard 2 safe state", nil, "HIGH"}
|
1,
|
||||||
|
{"invert endstops, z-axis", nil, true}
|
||||||
assert Param.to_human(:pin_guard_3_active_state, 0) ==
|
)
|
||||||
{"pin guard 3 safe state", nil, "HIGH"}
|
|
||||||
|
t(
|
||||||
assert Param.to_human(:pin_guard_4_active_state, 0) ==
|
:encoder_enabled_x,
|
||||||
{"pin guard 4 safe state", nil, "HIGH"}
|
1,
|
||||||
|
{"enable encoders / stall detection, x-axis", nil, true}
|
||||||
assert Param.to_human(:pin_guard_5_active_state, 0) ==
|
)
|
||||||
{"pin guard 5 safe state", nil, "HIGH"}
|
|
||||||
|
t(
|
||||||
|
:encoder_enabled_y,
|
||||||
|
1,
|
||||||
|
{"enable encoders / stall detection, y-axis", nil, true}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:encoder_enabled_z,
|
||||||
|
1,
|
||||||
|
{"enable encoders / stall detection, z-axis", nil, true}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:encoder_scaling_x,
|
||||||
|
float_value,
|
||||||
|
{"encoder scaling, x-axis", nil, "1.2"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:encoder_scaling_y,
|
||||||
|
float_value,
|
||||||
|
{"encoder scaling, y-axis", nil, "1.2"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:encoder_scaling_z,
|
||||||
|
float_value,
|
||||||
|
{"encoder scaling, z-axis", nil, "1.2"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:encoder_missed_steps_decay_x,
|
||||||
|
float_value,
|
||||||
|
{"missed step decay, x-axis", steps, "1.2"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:encoder_missed_steps_decay_y,
|
||||||
|
float_value,
|
||||||
|
{"missed step decay, y-axis", steps, "1.2"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:encoder_missed_steps_decay_z,
|
||||||
|
float_value,
|
||||||
|
{"missed step decay, z-axis", steps, "1.2"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:encoder_use_for_pos_x,
|
||||||
|
1,
|
||||||
|
{"use encoders for positioning, x-axis", nil, true}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:encoder_use_for_pos_y,
|
||||||
|
1,
|
||||||
|
{"use encoders for positioning, y-axis", nil, true}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:encoder_use_for_pos_z,
|
||||||
|
1,
|
||||||
|
{"use encoders for positioning, z-axis", nil, true}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:movement_axis_nr_steps_z,
|
||||||
|
1.0,
|
||||||
|
{"axis length, z-axis", "(steps)", "1"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:movement_axis_nr_steps_y,
|
||||||
|
1.0,
|
||||||
|
{"axis length, y-axis", "(steps)", "1"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:movement_axis_nr_steps_x,
|
||||||
|
1.0,
|
||||||
|
{"axis length, x-axis", "(steps)", "1"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:movement_steps_acc_dec_x,
|
||||||
|
1.0,
|
||||||
|
{"accelerate for, x-axis", "(steps)", "1"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:movement_steps_acc_dec_y,
|
||||||
|
1.0,
|
||||||
|
{"accelerate for, y-axis", "(steps)", "1"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:movement_steps_acc_dec_z,
|
||||||
|
1.0,
|
||||||
|
{"accelerate for, z-axis", "(steps)", "1"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:movement_home_up_x,
|
||||||
|
1.0,
|
||||||
|
{"negative coordinates only, x-axis", nil, true}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:movement_home_up_y,
|
||||||
|
1.0,
|
||||||
|
{"negative coordinates only, y-axis", nil, true}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:movement_home_up_z,
|
||||||
|
1.0,
|
||||||
|
{"negative coordinates only, z-axis", nil, true}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:encoder_missed_steps_max_x,
|
||||||
|
1.0,
|
||||||
|
{"max missed steps, x-axis", "(steps)", "1"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:encoder_missed_steps_max_y,
|
||||||
|
1.0,
|
||||||
|
{"max missed steps, y-axis", "(steps)", "1"}
|
||||||
|
)
|
||||||
|
|
||||||
|
t(
|
||||||
|
:encoder_missed_steps_max_z,
|
||||||
|
1.0,
|
||||||
|
{"max missed steps, z-axis", "(steps)", "1"}
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Handling of uknown parameters" do
|
test "Handling of uknown parameters" do
|
||||||
assert :unknown_parameter == Param.decode(-999)
|
log =
|
||||||
|
capture_log(fn ->
|
||||||
|
assert :unknown_parameter == Param.decode(-999)
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert log =~ "unknown firmware parameter: -999"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -100,9 +100,7 @@ if Mix.target() == :host do
|
||||||
else
|
else
|
||||||
import_config("target/#{Mix.env()}.exs")
|
import_config("target/#{Mix.env()}.exs")
|
||||||
|
|
||||||
if File.exists?("config/target/#{Mix.target()}.exs") do
|
import_config("target/#{Mix.target()}.exs")
|
||||||
import_config("target/#{Mix.target()}.exs")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if Mix.env() == :test do
|
if Mix.env() == :test do
|
||||||
|
|
|
@ -43,3 +43,5 @@ config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
||||||
firmware_flash_attempt_threshold: 0
|
firmware_flash_attempt_threshold: 0
|
||||||
|
|
||||||
config :plug, :validate_header_keys_during_test, true
|
config :plug, :validate_header_keys_during_test, true
|
||||||
|
|
||||||
|
config :ex_unit, capture_logs: true
|
||||||
|
|
|
@ -114,13 +114,6 @@ config :farmbot, FarmbotOS.Configurator,
|
||||||
config :farmbot, FarmbotOS.System,
|
config :farmbot, FarmbotOS.System,
|
||||||
system_tasks: FarmbotOS.Platform.Target.SystemTasks
|
system_tasks: FarmbotOS.Platform.Target.SystemTasks
|
||||||
|
|
||||||
config :nerves_hub,
|
|
||||||
client: FarmbotOS.Platform.Target.NervesHubClient,
|
|
||||||
remote_iex: true,
|
|
||||||
public_keys: [File.read!("priv/staging.pub"), File.read!("priv/prod.pub")]
|
|
||||||
|
|
||||||
config :nerves_hub, NervesHub.Socket, reconnect_interval: 5_000
|
|
||||||
|
|
||||||
config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 5
|
config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 5
|
||||||
|
|
||||||
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
config :farmbot_core, FarmbotCore.AssetWorker.FarmbotCore.Asset.FbosConfig,
|
||||||
|
|
|
@ -10,3 +10,7 @@ config :farmbot, FarmbotOS.Init.Supervisor,
|
||||||
init_children: [
|
init_children: [
|
||||||
FarmbotOS.Platform.Target.RTCWorker
|
FarmbotOS.Platform.Target.RTCWorker
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# :farmbot_firmware, FarmbotFirmware changes too much.
|
||||||
|
# Needed one that would stay stable, so I duplicated it here:
|
||||||
|
config :farmbot, FarmbotOS.SysCalls.FlashFirmware, gpio: Circuits.GPIO
|
||||||
|
|
|
@ -8,6 +8,10 @@ config :farmbot_core, FarmbotCore.FirmwareOpenTask, attempt_threshold: 50
|
||||||
config :farmbot_firmware, FarmbotFirmware,
|
config :farmbot_firmware, FarmbotFirmware,
|
||||||
reset: FarmbotOS.Platform.Target.FirmwareReset.GPIO
|
reset: FarmbotOS.Platform.Target.FirmwareReset.GPIO
|
||||||
|
|
||||||
|
# :farmbot_firmware, FarmbotFirmware changes too much.
|
||||||
|
# Needed one that would stay stable, so I duplicated it here:
|
||||||
|
config :farmbot, FarmbotOS.SysCalls.FlashFirmware, gpio: Circuits.GPIO
|
||||||
|
|
||||||
config :farmbot, FarmbotOS.Init.Supervisor,
|
config :farmbot, FarmbotOS.Init.Supervisor,
|
||||||
init_children: [
|
init_children: [
|
||||||
FarmbotOS.Platform.Target.RTCWorker
|
FarmbotOS.Platform.Target.RTCWorker
|
||||||
|
|
|
@ -17,7 +17,6 @@ defmodule Avrdude do
|
||||||
|
|
||||||
_ = File.stat!(hex_path)
|
_ = File.stat!(hex_path)
|
||||||
|
|
||||||
# STEP 1: Is the UART in use?
|
|
||||||
args = [
|
args = [
|
||||||
"-patmega2560",
|
"-patmega2560",
|
||||||
"-cwiring",
|
"-cwiring",
|
||||||
|
@ -25,16 +24,23 @@ defmodule Avrdude do
|
||||||
"-b#{@uart_speed}",
|
"-b#{@uart_speed}",
|
||||||
"-D",
|
"-D",
|
||||||
"-V",
|
"-V",
|
||||||
|
"-v",
|
||||||
"-Uflash:w:#{hex_path}:i"
|
"-Uflash:w:#{hex_path}:i"
|
||||||
]
|
]
|
||||||
|
|
||||||
# call the function for resetting the line before executing avrdude.
|
FarmbotCore.Logger.info(3, "Writing firmware to MCU...")
|
||||||
|
|
||||||
call_reset_fun(reset_fun)
|
call_reset_fun(reset_fun)
|
||||||
|
|
||||||
MuonTrap.cmd("avrdude", args,
|
result = MuonTrap.cmd("avrdude", args, stderr_to_stdout: true)
|
||||||
into: IO.stream(:stdio, :line),
|
|
||||||
stderr_to_stdout: true
|
if is_tuple(result) do
|
||||||
)
|
{a, exit_code} = result
|
||||||
|
FarmbotCore.Logger.info(3, inspect(a))
|
||||||
|
FarmbotCore.Logger.info(3, "Exit code #{exit_code}")
|
||||||
|
end
|
||||||
|
|
||||||
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
def call_reset_fun(reset_fun) do
|
def call_reset_fun(reset_fun) do
|
||||||
|
|
|
@ -20,16 +20,7 @@ defmodule FarmbotOS.Configurator.LoggerSocket do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl :cowboy_websocket
|
@impl :cowboy_websocket
|
||||||
def websocket_handle({:text, message}, state) do
|
def websocket_handle({:text, _}, state), do: {:ok, state}
|
||||||
case Jason.decode(message) do
|
|
||||||
{:ok, json} ->
|
|
||||||
websocket_handle({:json, json}, state)
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
_ = Logger.debug("discarding info: #{message}")
|
|
||||||
{:ok, state}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl :cowboy_websocket
|
@impl :cowboy_websocket
|
||||||
def websocket_info(:after_connect, state) do
|
def websocket_info(:after_connect, state) do
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule FarmbotOS.Lua.Ext.Firmware do
|
defmodule FarmbotOS.Lua.Ext.Firmware do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Lua extensions for interacting with the Firmware
|
Lua extensions for interacting with the Firmware
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias FarmbotCeleryScript.SysCalls
|
alias FarmbotCeleryScript.SysCalls
|
||||||
|
|
|
@ -4,7 +4,7 @@ defmodule FarmbotOS.SysCalls.Farmware do
|
||||||
require FarmbotCore.Logger
|
require FarmbotCore.Logger
|
||||||
alias FarmbotCore.{Asset, AssetSupervisor, FarmwareRuntime}
|
alias FarmbotCore.{Asset, AssetSupervisor, FarmwareRuntime}
|
||||||
alias FarmbotExt.API.ImageUploader
|
alias FarmbotExt.API.ImageUploader
|
||||||
@farmware_timeout 60_000
|
@farmware_timeout 1_200_000
|
||||||
|
|
||||||
def update_farmware(farmware_name) do
|
def update_farmware(farmware_name) do
|
||||||
with {:ok, installation} <- lookup_installation(farmware_name) do
|
with {:ok, installation} <- lookup_installation(farmware_name) do
|
||||||
|
@ -56,9 +56,9 @@ defmodule FarmbotOS.SysCalls.Farmware do
|
||||||
end
|
end
|
||||||
|
|
||||||
def farmware_timeout(farmware_runtime) do
|
def farmware_timeout(farmware_runtime) do
|
||||||
time = @farmware_timeout / 1_000
|
time = @farmware_timeout / 1_000 / 60
|
||||||
runtime = inspect(farmware_runtime)
|
runtime = inspect(farmware_runtime)
|
||||||
msg = "Farmware did not exit after #{time} seconds. Terminating #{runtime}"
|
msg = "Farmware did not exit after #{time} minutes. Terminating #{runtime}"
|
||||||
|
|
||||||
FarmbotCore.Logger.info(2, msg)
|
FarmbotCore.Logger.info(2, msg)
|
||||||
FarmwareRuntime.stop(farmware_runtime)
|
FarmwareRuntime.stop(farmware_runtime)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
Application.get_env(:farmbot, FarmbotOS.SysCalls.FlashFirmware, [])[:gpio]
|
||||||
|
|
||||||
defmodule FarmbotOS.SysCalls.FlashFirmware do
|
defmodule FarmbotOS.SysCalls.FlashFirmware do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
|
@ -5,6 +7,23 @@ defmodule FarmbotOS.SysCalls.FlashFirmware do
|
||||||
alias FarmbotFirmware
|
alias FarmbotFirmware
|
||||||
alias FarmbotCore.FirmwareTTYDetector
|
alias FarmbotCore.FirmwareTTYDetector
|
||||||
|
|
||||||
|
defmodule Stub do
|
||||||
|
require FarmbotCore.Logger
|
||||||
|
|
||||||
|
def fail do
|
||||||
|
m = "No express function found. Please notify support."
|
||||||
|
FarmbotCore.Logger.error(3, m)
|
||||||
|
{:error, m}
|
||||||
|
end
|
||||||
|
|
||||||
|
def open(_, _), do: fail()
|
||||||
|
def write(_, _), do: fail()
|
||||||
|
end
|
||||||
|
|
||||||
|
# This only matter for express.
|
||||||
|
# When it's an express, use Circuits.GPIO.
|
||||||
|
@gpio Application.get_env(:farmbot, __MODULE__, [])[:gpio] || Stub
|
||||||
|
|
||||||
import FarmbotFirmware.PackageUtils,
|
import FarmbotFirmware.PackageUtils,
|
||||||
only: [find_hex_file: 1, package_to_string: 1]
|
only: [find_hex_file: 1, package_to_string: 1]
|
||||||
|
|
||||||
|
@ -29,9 +48,7 @@ defmodule FarmbotOS.SysCalls.FlashFirmware do
|
||||||
),
|
),
|
||||||
:ok <- FarmbotFirmware.close_transport(),
|
:ok <- FarmbotFirmware.close_transport(),
|
||||||
_ <- FarmbotCore.Logger.debug(3, "starting firmware flash"),
|
_ <- FarmbotCore.Logger.debug(3, "starting firmware flash"),
|
||||||
{_, 0} <- Avrdude.flash(hex_file, tty, fun) do
|
_ <- finish_flashing(Avrdude.flash(hex_file, tty, fun)) do
|
||||||
FarmbotCore.Logger.success(2, "Firmware flashed successfully!")
|
|
||||||
|
|
||||||
%{firmware_path: tty}
|
%{firmware_path: tty}
|
||||||
|> Asset.update_fbos_config!()
|
|> Asset.update_fbos_config!()
|
||||||
|> Private.mark_dirty!(%{})
|
|> Private.mark_dirty!(%{})
|
||||||
|
@ -42,10 +59,18 @@ defmodule FarmbotOS.SysCalls.FlashFirmware do
|
||||||
{:error, reason}
|
{:error, reason}
|
||||||
|
|
||||||
error ->
|
error ->
|
||||||
{:error, "flash_firmware misc error: #{inspect(error)}"}
|
{:error, "flash_firmware returned #{inspect(error)}"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def finish_flashing({_result, 0}) do
|
||||||
|
FarmbotCore.Logger.success(2, "Firmware flashed successfully!")
|
||||||
|
end
|
||||||
|
|
||||||
|
def finish_flashing(result) do
|
||||||
|
FarmbotCore.Logger.debug(2, "AVR flash returned #{inspect(result)}")
|
||||||
|
end
|
||||||
|
|
||||||
defp find_tty() do
|
defp find_tty() do
|
||||||
case FirmwareTTYDetector.tty() do
|
case FirmwareTTYDetector.tty() do
|
||||||
nil ->
|
nil ->
|
||||||
|
@ -60,18 +85,30 @@ defmodule FarmbotOS.SysCalls.FlashFirmware do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp find_reset_fun("express_k10") do
|
defp find_reset_fun("express_k10") do
|
||||||
FarmbotCore.Logger.debug(3, "Using special reset function for express")
|
FarmbotCore.Logger.debug(3, "Using special express reset function")
|
||||||
# "magic" workaround to avoid compiler warnings.
|
{:ok, fn -> express_reset_fun() end}
|
||||||
# We used to inject this via App config, but it was
|
|
||||||
# error prone.
|
|
||||||
mod = :"Elixir.FarmbotOS.Platform.Target.FirmwareReset.GPIO"
|
|
||||||
fun = &mod.reset/0
|
|
||||||
{:ok, fun}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp find_reset_fun(_) do
|
defp find_reset_fun(_) do
|
||||||
FarmbotCore.Logger.debug(3, "Using default reset function")
|
FarmbotCore.Logger.debug(3, "Using default reset function")
|
||||||
fun = &FarmbotFirmware.NullReset.reset/0
|
{:ok, &FarmbotFirmware.NullReset.reset/0}
|
||||||
{:ok, fun}
|
end
|
||||||
|
|
||||||
|
def express_reset_fun() do
|
||||||
|
try do
|
||||||
|
FarmbotCore.Logger.debug(3, "Begin MCU reset")
|
||||||
|
{:ok, gpio} = @gpio.open(19, :output)
|
||||||
|
:ok = @gpio.write(gpio, 0)
|
||||||
|
:ok = @gpio.write(gpio, 1)
|
||||||
|
Process.sleep(1000)
|
||||||
|
:ok = @gpio.write(gpio, 0)
|
||||||
|
FarmbotCore.Logger.debug(3, "Finish MCU Reset")
|
||||||
|
:ok
|
||||||
|
rescue
|
||||||
|
ex ->
|
||||||
|
message = Exception.message(ex)
|
||||||
|
Logger.error("Could not flash express firmware: #{message}")
|
||||||
|
:express_reset_error
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,7 @@ defmodule FarmbotOS.Platform.Target.PinBindingWorker.CircuitsGPIOHandler do
|
||||||
{:ok, pin} = GPIO.open(pin_number, :input)
|
{:ok, pin} = GPIO.open(pin_number, :input)
|
||||||
:ok = GPIO.set_interrupts(pin, :rising)
|
:ok = GPIO.set_interrupts(pin, :rising)
|
||||||
# this has been checked on v1.3 and v1.5 hardware
|
# this has been checked on v1.3 and v1.5 hardware
|
||||||
# and it seems to be fine.
|
# and it seems to be fine.
|
||||||
:ok = GPIO.set_pull_mode(pin, :pulldown)
|
:ok = GPIO.set_pull_mode(pin, :pulldown)
|
||||||
{:ok, %{pin_number: pin_number, pin: pin, fun: fun, debounce: nil}}
|
{:ok, %{pin_number: pin_number, pin: pin, fun: fun, debounce: nil}}
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,17 +31,11 @@ defmodule FarmbotOs.AvrdudeTest do
|
||||||
"-b115200",
|
"-b115200",
|
||||||
"-D",
|
"-D",
|
||||||
"-V",
|
"-V",
|
||||||
|
"-v",
|
||||||
"-Uflash:w:/tmp/wow:i"
|
"-Uflash:w:/tmp/wow:i"
|
||||||
]
|
]
|
||||||
|
|
||||||
assert opts == [
|
assert opts == [stderr_to_stdout: true]
|
||||||
into: %IO.Stream{
|
|
||||||
device: :standard_io,
|
|
||||||
line_or_bytes: :line,
|
|
||||||
raw: false
|
|
||||||
},
|
|
||||||
stderr_to_stdout: true
|
|
||||||
]
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Avrdude.flash("/tmp/wow", "null", fn ->
|
Avrdude.flash("/tmp/wow", "null", fn ->
|
||||||
|
@ -62,17 +56,11 @@ defmodule FarmbotOs.AvrdudeTest do
|
||||||
"-b115200",
|
"-b115200",
|
||||||
"-D",
|
"-D",
|
||||||
"-V",
|
"-V",
|
||||||
|
"-v",
|
||||||
"-Uflash:w:/tmp/wow:i"
|
"-Uflash:w:/tmp/wow:i"
|
||||||
]
|
]
|
||||||
|
|
||||||
assert opts == [
|
assert opts == [stderr_to_stdout: true]
|
||||||
into: %IO.Stream{
|
|
||||||
device: :standard_io,
|
|
||||||
line_or_bytes: :line,
|
|
||||||
raw: false
|
|
||||||
},
|
|
||||||
stderr_to_stdout: true
|
|
||||||
]
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Avrdude.flash("/tmp/wow", "/dev/null", fn ->
|
Avrdude.flash("/tmp/wow", "/dev/null", fn ->
|
||||||
|
|
|
@ -3,12 +3,28 @@ defmodule FarmbotOS.Configurator.LoggerSocketTest do
|
||||||
use Mimic
|
use Mimic
|
||||||
alias FarmbotOS.Configurator.LoggerSocket
|
alias FarmbotOS.Configurator.LoggerSocket
|
||||||
setup :verify_on_exit!
|
setup :verify_on_exit!
|
||||||
|
import ExUnit.CaptureLog
|
||||||
|
|
||||||
test "init/2" do
|
test "init/2" do
|
||||||
# TODO(Rick) Not sure what the real args are.
|
|
||||||
# Circle back to make this test more realistic
|
|
||||||
# later.
|
|
||||||
expected = {:cowboy_websocket, :foo, :bar}
|
expected = {:cowboy_websocket, :foo, :bar}
|
||||||
assert expected == LoggerSocket.init(:foo, :bar)
|
assert expected == LoggerSocket.init(:foo, :bar)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "websocket_init" do
|
||||||
|
assert {:ok, %{}} == LoggerSocket.websocket_init(nil)
|
||||||
|
assert_receive :after_connect
|
||||||
|
end
|
||||||
|
|
||||||
|
test "websocket_handle (invalid JSON)" do
|
||||||
|
s = %{state: :yep}
|
||||||
|
msg = "Not JSON."
|
||||||
|
payl = {:text, msg}
|
||||||
|
assert {:ok, s} == LoggerSocket.websocket_handle(payl, s)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "websocket_info/2" do
|
||||||
|
assert capture_log(fn ->
|
||||||
|
LoggerSocket.websocket_info(:whatever, %{})
|
||||||
|
end) =~ "Dropping :whatever"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,6 +8,8 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
use Mimic
|
use Mimic
|
||||||
setup :verify_on_exit!
|
setup :verify_on_exit!
|
||||||
|
|
||||||
|
import ExUnit.CaptureIO
|
||||||
|
|
||||||
@opts Router.init([])
|
@opts Router.init([])
|
||||||
# Stolen from https://github.com/phoenixframework/phoenix/blob/3f157c30ceae8d1eb524fdd05b5e3de10e434c42/lib/phoenix/test/conn_test.ex#L438
|
# Stolen from https://github.com/phoenixframework/phoenix/blob/3f157c30ceae8d1eb524fdd05b5e3de10e434c42/lib/phoenix/test/conn_test.ex#L438
|
||||||
defp redirected_to(conn, status \\ 302)
|
defp redirected_to(conn, status \\ 302)
|
||||||
|
@ -34,6 +36,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
Router.call(conn, @opts)
|
Router.call(conn, @opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "index after reset" do
|
test "index after reset" do
|
||||||
FarmbotOS.Configurator.ConfigDataLayer
|
FarmbotOS.Configurator.ConfigDataLayer
|
||||||
|> expect(:load_last_reset_reason, fn -> "whoops!" end)
|
|> expect(:load_last_reset_reason, fn -> "whoops!" end)
|
||||||
|
@ -45,6 +48,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
assert conn.resp_body =~ "whoops!"
|
assert conn.resp_body =~ "whoops!"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "redirects" do
|
test "redirects" do
|
||||||
redirects = [
|
redirects = [
|
||||||
"/check_network_status.txt",
|
"/check_network_status.txt",
|
||||||
|
@ -66,6 +70,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "celeryscript requests don't get listed as last reset reason" do
|
test "celeryscript requests don't get listed as last reset reason" do
|
||||||
FarmbotOS.Configurator.ConfigDataLayer
|
FarmbotOS.Configurator.ConfigDataLayer
|
||||||
|> expect(:load_last_reset_reason, fn -> "CeleryScript request." end)
|
|> expect(:load_last_reset_reason, fn -> "CeleryScript request." end)
|
||||||
|
@ -75,6 +80,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
refute conn.resp_body =~ "CeleryScript request."
|
refute conn.resp_body =~ "CeleryScript request."
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "no reset reason" do
|
test "no reset reason" do
|
||||||
FarmbotOS.Configurator.ConfigDataLayer
|
FarmbotOS.Configurator.ConfigDataLayer
|
||||||
|> expect(:load_last_reset_reason, fn -> nil end)
|
|> expect(:load_last_reset_reason, fn -> nil end)
|
||||||
|
@ -84,6 +90,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
refute conn.resp_body =~ "<div class=\"last-shutdown-reason\">"
|
refute conn.resp_body =~ "<div class=\"last-shutdown-reason\">"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "captive portal" do
|
test "captive portal" do
|
||||||
conn = conn(:get, "/generate_204")
|
conn = conn(:get, "/generate_204")
|
||||||
conn = Router.call(conn, @opts)
|
conn = Router.call(conn, @opts)
|
||||||
|
@ -94,6 +101,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
assert conn.status == 302
|
assert conn.status == 302
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "network index" do
|
test "network index" do
|
||||||
FarmbotOS.Configurator.FakeNetworkLayer
|
FarmbotOS.Configurator.FakeNetworkLayer
|
||||||
|> expect(:list_interfaces, fn ->
|
|> expect(:list_interfaces, fn ->
|
||||||
|
@ -108,6 +116,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
assert conn.resp_body =~ "eth0"
|
assert conn.resp_body =~ "eth0"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "select network sets session data" do
|
test "select network sets session data" do
|
||||||
conn = conn(:post, "select_interface")
|
conn = conn(:post, "select_interface")
|
||||||
conn = Router.call(conn, @opts)
|
conn = Router.call(conn, @opts)
|
||||||
|
@ -124,6 +133,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
assert get_session(conn, "ifname") == "wlan0"
|
assert get_session(conn, "ifname") == "wlan0"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "config wired" do
|
test "config wired" do
|
||||||
conn =
|
conn =
|
||||||
conn(:get, "/config_wired")
|
conn(:get, "/config_wired")
|
||||||
|
@ -133,6 +143,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
assert conn.resp_body =~ "Advanced settings"
|
assert conn.resp_body =~ "Advanced settings"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "config wireless SSID list" do
|
test "config wireless SSID list" do
|
||||||
FarmbotOS.Configurator.FakeNetworkLayer
|
FarmbotOS.Configurator.FakeNetworkLayer
|
||||||
|> expect(:scan, fn _ ->
|
|> expect(:scan, fn _ ->
|
||||||
|
@ -154,6 +165,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
assert conn.resp_body =~ "Test Network"
|
assert conn.resp_body =~ "Test Network"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "config wireless" do
|
test "config wireless" do
|
||||||
# No SSID or SECURITY
|
# No SSID or SECURITY
|
||||||
conn =
|
conn =
|
||||||
|
@ -238,6 +250,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
assert conn.resp_body =~ "unknown or unsupported"
|
assert conn.resp_body =~ "unknown or unsupported"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "config_network" do
|
test "config_network" do
|
||||||
params = %{
|
params = %{
|
||||||
"dns_name" => "super custom",
|
"dns_name" => "super custom",
|
||||||
|
@ -290,6 +303,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
assert redirected_to(conn) == "/credentials"
|
assert redirected_to(conn) == "/credentials"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "credentials index" do
|
test "credentials index" do
|
||||||
FarmbotOS.Configurator.ConfigDataLayer
|
FarmbotOS.Configurator.ConfigDataLayer
|
||||||
|> expect(:load_email, fn -> "test@test.org" end)
|
|> expect(:load_email, fn -> "test@test.org" end)
|
||||||
|
@ -302,6 +316,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
assert conn.resp_body =~ "https://my.farm.bot"
|
assert conn.resp_body =~ "https://my.farm.bot"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "configure credentials" do
|
test "configure credentials" do
|
||||||
params = %{
|
params = %{
|
||||||
"email" => "test@test.org",
|
"email" => "test@test.org",
|
||||||
|
@ -334,6 +349,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
assert redirected_to(conn) == "/credentials"
|
assert redirected_to(conn) == "/credentials"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "finish" do
|
test "finish" do
|
||||||
conn =
|
conn =
|
||||||
conn(:get, "/finish")
|
conn(:get, "/finish")
|
||||||
|
@ -342,6 +358,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
assert redirected_to(conn) == "/"
|
assert redirected_to(conn) == "/"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "404" do
|
test "404" do
|
||||||
conn =
|
conn =
|
||||||
conn(:get, "/whoops")
|
conn(:get, "/whoops")
|
||||||
|
@ -350,6 +367,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
assert conn.resp_body == "Page not found"
|
assert conn.resp_body == "Page not found"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "500" do
|
test "500" do
|
||||||
FarmbotOS.Configurator.FakeNetworkLayer
|
FarmbotOS.Configurator.FakeNetworkLayer
|
||||||
|> expect(:scan, fn _ ->
|
|> expect(:scan, fn _ ->
|
||||||
|
@ -360,20 +378,26 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
]
|
]
|
||||||
end)
|
end)
|
||||||
|
|
||||||
conn =
|
crasher = fn ->
|
||||||
conn(:get, "/config_wireless")
|
conn =
|
||||||
|> init_test_session(%{"ifname" => "wlan0"})
|
conn(:get, "/config_wireless")
|
||||||
|> Router.call(@opts)
|
|> init_test_session(%{"ifname" => "wlan0"})
|
||||||
|
|> Router.call(@opts)
|
||||||
|
|
||||||
assert conn.status == 500
|
assert conn.status == 500
|
||||||
|
end
|
||||||
|
|
||||||
|
assert capture_io(:stderr, crasher) =~ "render error"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "/scheduler_debugger" do
|
test "/scheduler_debugger" do
|
||||||
kon = get_con("/scheduler_debugger")
|
kon = get_con("/scheduler_debugger")
|
||||||
assert String.contains?(kon.resp_body, "scheduler_debugger.js")
|
assert String.contains?(kon.resp_body, "scheduler_debugger.js")
|
||||||
assert String.contains?(kon.resp_body, "<title>Scheduler Debugger</title>")
|
assert String.contains?(kon.resp_body, "<title>Scheduler Debugger</title>")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "/logger" do
|
test "/logger" do
|
||||||
kon = get_con("/logger")
|
kon = get_con("/logger")
|
||||||
|
|
||||||
|
@ -391,6 +415,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "/api/telemetry/cpu_usage" do
|
test "/api/telemetry/cpu_usage" do
|
||||||
{:ok, json} = Jason.decode(get_con("/api/telemetry/cpu_usage").resp_body)
|
{:ok, json} = Jason.decode(get_con("/api/telemetry/cpu_usage").resp_body)
|
||||||
assert Enum.count(json) == 10
|
assert Enum.count(json) == 10
|
||||||
|
@ -400,6 +425,7 @@ defmodule FarmbotOS.Configurator.RouterTest do
|
||||||
assert(is_integer(zero["value"]))
|
assert(is_integer(zero["value"]))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "/finish" do
|
test "/finish" do
|
||||||
expect(ConfigDataLayer, :save_config, 1, fn _conf ->
|
expect(ConfigDataLayer, :save_config, 1, fn _conf ->
|
||||||
:ok
|
:ok
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
defmodule FarmbotOS.Lua.Ext.FirmwareTest do
|
||||||
|
alias FarmbotOS.Lua.Ext.Firmware
|
||||||
|
use ExUnit.Case
|
||||||
|
use Mimic
|
||||||
|
setup :verify_on_exit!
|
||||||
|
|
||||||
|
test "calibrate/2" do
|
||||||
|
msg = "expected stub error"
|
||||||
|
lua = "return"
|
||||||
|
|
||||||
|
expect(FarmbotCeleryScript.SysCalls, :calibrate, 2, fn
|
||||||
|
"x" -> :ok
|
||||||
|
_ -> {:error, msg}
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {[true], ^lua} = Firmware.calibrate(["x"], lua)
|
||||||
|
assert {[nil, ^msg], ^lua} = Firmware.calibrate(["y"], lua)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "move_absolute/2" do
|
||||||
|
msg = "expected stub error"
|
||||||
|
lua = "return"
|
||||||
|
|
||||||
|
expect(FarmbotCeleryScript.SysCalls, :move_absolute, 4, fn
|
||||||
|
1, _, _, _ -> :ok
|
||||||
|
_, _, _, _ -> {:error, msg}
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {[true], ^lua} = Firmware.move_absolute([1, 2, 3, 4], lua)
|
||||||
|
assert {[nil, ^msg], ^lua} = Firmware.move_absolute([5, 6, 7, 8], lua)
|
||||||
|
assert {[true], ^lua} = Firmware.move_absolute([1, 2, 3], lua)
|
||||||
|
assert {[nil, ^msg], ^lua} = Firmware.move_absolute([5, 6, 7], lua)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "find_home/2" do
|
||||||
|
msg = "expected stub error"
|
||||||
|
lua = "return"
|
||||||
|
|
||||||
|
expect(FarmbotCeleryScript.SysCalls, :find_home, 2, fn
|
||||||
|
"x" -> :ok
|
||||||
|
_ -> {:error, msg}
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {[true], ^lua} = Firmware.find_home(["x"], lua)
|
||||||
|
assert {[nil, ^msg], ^lua} = Firmware.find_home(["y"], lua)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "emergency_lock/2" do
|
||||||
|
msg = "expected stub error"
|
||||||
|
lua = "return"
|
||||||
|
|
||||||
|
expect(FarmbotCeleryScript.SysCalls, :emergency_lock, 1, fn -> :ok end)
|
||||||
|
assert {[true], ^lua} = Firmware.emergency_lock(:ok, lua)
|
||||||
|
|
||||||
|
expect(FarmbotCeleryScript.SysCalls, :emergency_lock, 1, fn ->
|
||||||
|
{:error, msg}
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {[nil, ^msg], ^lua} = Firmware.emergency_lock(nil, lua)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "emergency_unlock/2" do
|
||||||
|
msg = "expected stub error"
|
||||||
|
lua = "return"
|
||||||
|
|
||||||
|
expect(FarmbotCeleryScript.SysCalls, :emergency_unlock, 1, fn -> :ok end)
|
||||||
|
assert {[true], ^lua} = Firmware.emergency_unlock(:ok, lua)
|
||||||
|
|
||||||
|
expect(FarmbotCeleryScript.SysCalls, :emergency_unlock, 1, fn ->
|
||||||
|
{:error, msg}
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {[nil, ^msg], ^lua} = Firmware.emergency_unlock(nil, lua)
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,6 +4,7 @@ defmodule FarmbotOS.LuaTest do
|
||||||
setup :verify_on_exit!
|
setup :verify_on_exit!
|
||||||
alias FarmbotOS.Lua
|
alias FarmbotOS.Lua
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "evaluates Lua" do
|
test "evaluates Lua" do
|
||||||
assert Lua.eval_assertion("Returns 'true'", "return true")
|
assert Lua.eval_assertion("Returns 'true'", "return true")
|
||||||
{:error, message1} = Lua.eval_assertion("Returns 'true'", "-1")
|
{:error, message1} = Lua.eval_assertion("Returns 'true'", "-1")
|
||||||
|
|
|
@ -11,6 +11,7 @@ defmodule FarmbotOS.SysCallsTest do
|
||||||
|
|
||||||
use Mimic
|
use Mimic
|
||||||
setup :verify_on_exit!
|
setup :verify_on_exit!
|
||||||
|
import ExUnit.CaptureIO
|
||||||
|
|
||||||
test "emergency_unlock" do
|
test "emergency_unlock" do
|
||||||
expect(FarmbotFirmware, :command, fn {:command_emergency_unlock, []} ->
|
expect(FarmbotFirmware, :command, fn {:command_emergency_unlock, []} ->
|
||||||
|
@ -71,6 +72,7 @@ defmodule FarmbotOS.SysCallsTest do
|
||||||
assert {:error, "Could not find peripheral by id: 11"} == result6
|
assert {:error, "Could not find peripheral by id: 11"} == result6
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "sync() success" do
|
test "sync() success" do
|
||||||
# Expect 5 calls and an :ok response.
|
# Expect 5 calls and an :ok response.
|
||||||
expect(FarmbotExt.API.Reconciler, :sync_group, 5, fn changeset, _group ->
|
expect(FarmbotExt.API.Reconciler, :sync_group, 5, fn changeset, _group ->
|
||||||
|
@ -81,16 +83,21 @@ defmodule FarmbotOS.SysCallsTest do
|
||||||
{:ok, %{wut: module}}
|
{:ok, %{wut: module}}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert :ok == SysCalls.sync()
|
assert capture_io(fn ->
|
||||||
|
assert :ok == SysCalls.sync()
|
||||||
|
end) =~ "green really_fast_blink"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "sync() failure" do
|
test "sync() failure" do
|
||||||
# Expect 5 calls and an :ok response.
|
# Expect 5 calls and an :ok response.
|
||||||
expect(FarmbotExt.API, :get_changeset, fn FarmbotCore.Asset.Sync ->
|
expect(FarmbotExt.API, :get_changeset, fn FarmbotCore.Asset.Sync ->
|
||||||
"this is a test"
|
"this is a test"
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert {:error, "\"this is a test\""} == SysCalls.sync()
|
assert capture_io(fn ->
|
||||||
|
assert {:error, "\"this is a test\""} == SysCalls.sync()
|
||||||
|
end) =~ "green slow_blink"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "get_sequence(id)" do
|
test "get_sequence(id)" do
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule FarmbotOS.SysCalls.FarmwareTest do
|
||||||
|
|
||||||
expect(FarmbotCore.LogExecutor, :execute, fn log ->
|
expect(FarmbotCore.LogExecutor, :execute, fn log ->
|
||||||
expected =
|
expected =
|
||||||
"Farmware did not exit after 60.0 seconds. Terminating :FAKE_PID"
|
"Farmware did not exit after 20.0 minutes. Terminating :FAKE_PID"
|
||||||
|
|
||||||
assert log.message == expected
|
assert log.message == expected
|
||||||
:ok
|
:ok
|
||||||
|
|
|
@ -91,6 +91,7 @@ defmodule FarmbotOS.SysCalls.MovementTest do
|
||||||
assert msg == error_log
|
assert msg == error_log
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "move_absolute/4 - error (in tuple)" do
|
test "move_absolute/4 - error (in tuple)" do
|
||||||
expect(FarmbotFirmware, :request, 1, fn {:parameter_read, [_]} ->
|
expect(FarmbotFirmware, :request, 1, fn {:parameter_read, [_]} ->
|
||||||
{:error, "boom"}
|
{:error, "boom"}
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule FarmbotOS.SysCalls.PinControlTest do
|
||||||
alias FarmbotCore.Asset.Peripheral
|
alias FarmbotCore.Asset.Peripheral
|
||||||
@digital 0
|
@digital 0
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "read_pin with %Peripheral{}, pin is 1" do
|
test "read_pin with %Peripheral{}, pin is 1" do
|
||||||
expect(FarmbotFirmware, :request, 1, fn
|
expect(FarmbotFirmware, :request, 1, fn
|
||||||
{:pin_read, [p: 13, m: 0]} ->
|
{:pin_read, [p: 13, m: 0]} ->
|
||||||
|
@ -20,6 +21,7 @@ defmodule FarmbotOS.SysCalls.PinControlTest do
|
||||||
assert 1 == PinControl.read_pin(peripheral, @digital)
|
assert 1 == PinControl.read_pin(peripheral, @digital)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "read_pin with %Peripheral{}, pin is 0" do
|
test "read_pin with %Peripheral{}, pin is 0" do
|
||||||
expect(FarmbotFirmware, :request, 1, fn
|
expect(FarmbotFirmware, :request, 1, fn
|
||||||
{:pin_read, [p: 13, m: 0]} ->
|
{:pin_read, [p: 13, m: 0]} ->
|
||||||
|
@ -30,6 +32,7 @@ defmodule FarmbotOS.SysCalls.PinControlTest do
|
||||||
assert 0 == PinControl.read_pin(peripheral, @digital)
|
assert 0 == PinControl.read_pin(peripheral, @digital)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "toggle_pin, 1 => 0" do
|
test "toggle_pin, 1 => 0" do
|
||||||
expect(FarmbotCore.Asset, :get_peripheral_by_pin, 1, fn 12 ->
|
expect(FarmbotCore.Asset, :get_peripheral_by_pin, 1, fn 12 ->
|
||||||
nil
|
nil
|
||||||
|
@ -48,6 +51,7 @@ defmodule FarmbotOS.SysCalls.PinControlTest do
|
||||||
assert :ok = PinControl.toggle_pin(12)
|
assert :ok = PinControl.toggle_pin(12)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "toggle_pin, 0 => 1" do
|
test "toggle_pin, 0 => 1" do
|
||||||
expect(FarmbotCore.Asset, :get_peripheral_by_pin, 1, fn 12 ->
|
expect(FarmbotCore.Asset, :get_peripheral_by_pin, 1, fn 12 ->
|
||||||
nil
|
nil
|
||||||
|
@ -71,15 +75,14 @@ defmodule FarmbotOS.SysCalls.PinControlTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "set_servo_angle" do
|
test "set_servo_angle" do
|
||||||
expect(FarmbotFirmware, :command, 1, fn
|
expect(FarmbotFirmware, :command, 2, fn
|
||||||
{:servo_write, [p: 20, v: 90]} -> {:error, "opps"}
|
{:servo_write, [p: 20, v: 90]} -> {:error, "opps"}
|
||||||
{:servo_write, [p: 40, v: 180]} -> :ok
|
{:servo_write, [p: 40, v: 180]} -> :ok
|
||||||
end)
|
end)
|
||||||
|
|
||||||
assert :ok = PinControl.set_servo_angle(40, 180)
|
assert :ok = PinControl.set_servo_angle(40, 180)
|
||||||
|
|
||||||
message =
|
message = "Firmware error @ \"set_servo_angle\": \"opps\""
|
||||||
"Firmware error @ \"set_servo_angle\": \"Can't send command when in :transport_boot state\""
|
|
||||||
|
|
||||||
assert {:error, ^message} = PinControl.set_servo_angle(20, 90)
|
assert {:error, ^message} = PinControl.set_servo_angle(20, 90)
|
||||||
end
|
end
|
||||||
|
|
|
@ -101,6 +101,7 @@ defmodule FarmbotOS.SysCalls.PointLookupTest do
|
||||||
assert pg == PointLookup.get_point_group(pg.id)
|
assert pg == PointLookup.get_point_group(pg.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag :capture_log
|
||||||
test "PointLookup.get_point_group/1 - string" do
|
test "PointLookup.get_point_group/1 - string" do
|
||||||
Repo.delete_all(PointGroup)
|
Repo.delete_all(PointGroup)
|
||||||
Repo.delete_all(Point)
|
Repo.delete_all(Point)
|
||||||
|
|
|
@ -1,20 +1,26 @@
|
||||||
Application.ensure_all_started(:mimic)
|
Application.ensure_all_started(:mimic)
|
||||||
Mimic.copy(FarmbotCore.Asset.Device)
|
|
||||||
Mimic.copy(FarmbotCore.Asset.FbosConfig)
|
[
|
||||||
Mimic.copy(FarmbotCore.Asset.FirmwareConfig)
|
FarmbotCore.Asset.Device,
|
||||||
Mimic.copy(FarmbotCore.Asset)
|
FarmbotCore.Asset.FbosConfig,
|
||||||
Mimic.copy(FarmbotCore.BotState)
|
FarmbotCore.Asset.FirmwareConfig,
|
||||||
Mimic.copy(FarmbotCore.Config)
|
FarmbotCore.Asset,
|
||||||
Mimic.copy(FarmbotCore.FarmwareRuntime)
|
FarmbotCore.BotState,
|
||||||
Mimic.copy(FarmbotCore.LogExecutor)
|
FarmbotCore.Config,
|
||||||
Mimic.copy(FarmbotExt.API.Reconciler)
|
FarmbotCore.FarmwareRuntime,
|
||||||
Mimic.copy(FarmbotExt.API)
|
FarmbotCore.LogExecutor,
|
||||||
Mimic.copy(FarmbotFirmware)
|
FarmbotExt.API.Reconciler,
|
||||||
Mimic.copy(FarmbotOS.Configurator.ConfigDataLayer)
|
FarmbotExt.API,
|
||||||
Mimic.copy(FarmbotOS.Configurator.DetsTelemetryLayer)
|
FarmbotFirmware,
|
||||||
Mimic.copy(FarmbotOS.Configurator.FakeNetworkLayer)
|
FarmbotOS.Configurator.ConfigDataLayer,
|
||||||
Mimic.copy(FarmbotOS.SysCalls.Movement)
|
FarmbotOS.Configurator.DetsTelemetryLayer,
|
||||||
Mimic.copy(FarmbotOS.SysCalls)
|
FarmbotOS.Configurator.FakeNetworkLayer,
|
||||||
Mimic.copy(File)
|
FarmbotOS.SysCalls.Movement,
|
||||||
Mimic.copy(MuonTrap)
|
FarmbotOS.SysCalls,
|
||||||
|
File,
|
||||||
|
MuonTrap,
|
||||||
|
FarmbotCeleryScript.SysCalls
|
||||||
|
]
|
||||||
|
|> Enum.map(&Mimic.copy/1)
|
||||||
|
|
||||||
ExUnit.start()
|
ExUnit.start()
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
defmodule Mix.Tasks.Farmbot.Coveralls do
|
|
||||||
@moduledoc """
|
|
||||||
Mix Task to report the coverage for all of the individual projects that make
|
|
||||||
up the repository.
|
|
||||||
"""
|
|
||||||
|
|
||||||
use Mix.Task
|
|
||||||
Module.register_attribute(__MODULE__, :projects, accumulate: true)
|
|
||||||
@projects :farmbot_celery_script
|
|
||||||
@projects :farmbot_core
|
|
||||||
@projects :farmbot_ext
|
|
||||||
@projects :farmbot_firmware
|
|
||||||
@projects :farmbot_os
|
|
||||||
# @projects :farmbot_telemetry
|
|
||||||
|
|
||||||
def run(args) do
|
|
||||||
@projects
|
|
||||||
|> pmap(&read_coverage_json!/1)
|
|
||||||
|> List.flatten()
|
|
||||||
|> run_task(args)
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_task(stats, []) do
|
|
||||||
run_task(stats, ["local"])
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_task(stats, ["local"]) do
|
|
||||||
ExCoveralls.Local.execute(stats, [])
|
|
||||||
end
|
|
||||||
|
|
||||||
def run_task(stats, ["circle"]) do
|
|
||||||
ExCoveralls.Circle.execute(stats, [])
|
|
||||||
end
|
|
||||||
|
|
||||||
def pmap(data, func) do
|
|
||||||
data
|
|
||||||
|> Enum.map(&(Task.async(fn -> func.(&1) end)))
|
|
||||||
|> Enum.map(&Task.await/1)
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_coverage_json!(project) do
|
|
||||||
coverage_file = Path.join([to_string(project), "cover", "excoveralls.json"])
|
|
||||||
with {:ok, bin} <- File.read(coverage_file),
|
|
||||||
{:ok, json} <- Jason.decode(bin) do
|
|
||||||
Enum.map(json["source_files"], fn(%{"name" => name, "source" => source, "coverage" => coverage}) ->
|
|
||||||
%{name: Path.join([to_string(project), name]), source: source, coverage: coverage}
|
|
||||||
end)
|
|
||||||
else
|
|
||||||
_ -> Mix.raise("""
|
|
||||||
Could not read coverage JSON from #{coverage_file}.
|
|
||||||
Make sure to run `mix coveralls.json` in each project's parent
|
|
||||||
directory.
|
|
||||||
""")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,6 +1,4 @@
|
||||||
defmodule Farmbot.TestSupport.AssetFixtures do
|
defmodule Farmbot.TestSupport.AssetFixtures do
|
||||||
alias FarmbotCore.Asset
|
|
||||||
|
|
||||||
alias FarmbotCore.Asset.{
|
alias FarmbotCore.Asset.{
|
||||||
Device,
|
Device,
|
||||||
FarmEvent,
|
FarmEvent,
|
||||||
|
@ -10,12 +8,12 @@ defmodule Farmbot.TestSupport.AssetFixtures do
|
||||||
Sequence
|
Sequence
|
||||||
}
|
}
|
||||||
|
|
||||||
def regimen_instance(regimen_params, farm_event_params, params \\ %{}) do
|
# def regimen_instance(regimen_params, farm_event_params, params \\ %{}) do
|
||||||
regimen = regimen(regimen_params)
|
# regimen = regimen(regimen_params)
|
||||||
farm_event = regimen_event(regimen, farm_event_params)
|
# farm_event = regimen_event(regimen, farm_event_params)
|
||||||
params = Map.merge(%{id: :rand.uniform(10000), monitor: false}, params)
|
# params = Map.merge(%{id: :rand.uniform(10000), monitor: false}, params)
|
||||||
Asset.new_regimen_instance!(farm_event, params)
|
# Asset.new_regimen_instance!(farm_event, params)
|
||||||
end
|
# end
|
||||||
|
|
||||||
def fbos_config(params \\ %{}) do
|
def fbos_config(params \\ %{}) do
|
||||||
default = %{
|
default = %{
|
||||||
|
@ -77,30 +75,6 @@ defmodule Farmbot.TestSupport.AssetFixtures do
|
||||||
|> Repo.insert!()
|
|> Repo.insert!()
|
||||||
end
|
end
|
||||||
|
|
||||||
def sequence_event(sequence, params \\ %{}) do
|
|
||||||
now = DateTime.utc_now()
|
|
||||||
|
|
||||||
params =
|
|
||||||
Map.merge(
|
|
||||||
%{
|
|
||||||
id: :rand.uniform(1_000_000),
|
|
||||||
monitor: false,
|
|
||||||
executable_type: "Sequence",
|
|
||||||
executable_id: sequence.id,
|
|
||||||
start_time: now,
|
|
||||||
end_time: now,
|
|
||||||
repeat: 0,
|
|
||||||
time_unit: "never"
|
|
||||||
},
|
|
||||||
params
|
|
||||||
)
|
|
||||||
|
|
||||||
FarmEvent
|
|
||||||
|> struct()
|
|
||||||
|> FarmEvent.changeset(params)
|
|
||||||
|> Repo.insert!()
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Instantiates, but does not create, a %Device{}
|
Instantiates, but does not create, a %Device{}
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue