Start splitting Farmbot Application Poncho Style
This begins splitting the monolithic Farmbot application into separate, independent OTP applicatoins.pull/974/head
parent
1fe52021c7
commit
cf1ef23b17
159
.credo.exs
159
.credo.exs
|
@ -1,159 +0,0 @@
|
|||
# This file contains the configuration for Credo and you are probably reading
|
||||
# this after creating it with `mix credo.gen.config`.
|
||||
#
|
||||
# If you find anything wrong or unclear in this file, please report an
|
||||
# issue on GitHub: https://github.com/rrrene/credo/issues
|
||||
#
|
||||
%{
|
||||
#
|
||||
# You can have as many configs as you like in the `configs:` field.
|
||||
configs: [
|
||||
%{
|
||||
#
|
||||
# Run any exec using `mix credo -C <name>`. If no exec name is given
|
||||
# "default" is used.
|
||||
#
|
||||
name: "default",
|
||||
#
|
||||
# These are the files included in the analysis:
|
||||
files: %{
|
||||
#
|
||||
# You can give explicit globs or simply directories.
|
||||
# In the latter case `**/*.{ex,exs}` will be used.
|
||||
#
|
||||
included: ["lib/", "src/"],
|
||||
excluded: [~r"/_build/", ~r"/deps/"]
|
||||
},
|
||||
#
|
||||
# If you create your own checks, you must specify the source files for
|
||||
# them here, so they can be loaded by Credo before running the analysis.
|
||||
#
|
||||
requires: [],
|
||||
#
|
||||
# If you want to enforce a style guide and need a more traditional linting
|
||||
# experience, you can change `strict` to `true` below:
|
||||
#
|
||||
strict: false,
|
||||
#
|
||||
# If you want to use uncolored output by default, you can change `color`
|
||||
# to `false` below:
|
||||
#
|
||||
color: true,
|
||||
#
|
||||
# You can customize the parameters of any check by adding a second element
|
||||
# to the tuple.
|
||||
#
|
||||
# To disable a check put `false` as second element:
|
||||
#
|
||||
# {Credo.Check.Design.DuplicatedCode, false}
|
||||
#
|
||||
checks: [
|
||||
#
|
||||
## Consistency Checks
|
||||
#
|
||||
{Credo.Check.Consistency.ExceptionNames},
|
||||
{Credo.Check.Consistency.LineEndings},
|
||||
{Credo.Check.Consistency.ParameterPatternMatching},
|
||||
{Credo.Check.Consistency.SpaceAroundOperators},
|
||||
{Credo.Check.Consistency.SpaceInParentheses},
|
||||
{Credo.Check.Consistency.TabsOrSpaces},
|
||||
|
||||
#
|
||||
## Design Checks
|
||||
#
|
||||
# You can customize the priority of any check
|
||||
# Priority values are: `low, normal, high, higher`
|
||||
#
|
||||
{Credo.Check.Design.AliasUsage, false},
|
||||
# For some checks, you can also set other parameters
|
||||
#
|
||||
# If you don't want the `setup` and `test` macro calls in ExUnit tests
|
||||
# or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just
|
||||
# set the `excluded_macros` parameter to `[:schema, :setup, :test]`.
|
||||
#
|
||||
{Credo.Check.Design.DuplicatedCode, excluded_macros: []},
|
||||
# You can also customize the exit_status of each check.
|
||||
# If you don't want TODO comments to cause `mix credo` to fail, just
|
||||
# set this value to 0 (zero).
|
||||
#
|
||||
{Credo.Check.Design.TagTODO, false},
|
||||
{Credo.Check.Design.TagFIXME, false},
|
||||
|
||||
#
|
||||
## Readability Checks
|
||||
#
|
||||
{Credo.Check.Readability.FunctionNames},
|
||||
{Credo.Check.Readability.LargeNumbers},
|
||||
{Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 80},
|
||||
{Credo.Check.Readability.ModuleAttributeNames},
|
||||
{Credo.Check.Readability.ModuleDoc},
|
||||
{Credo.Check.Readability.ModuleNames},
|
||||
{Credo.Check.Readability.ParenthesesOnZeroArityDefs},
|
||||
{Credo.Check.Readability.ParenthesesInCondition},
|
||||
{Credo.Check.Readability.PredicateFunctionNames},
|
||||
{Credo.Check.Readability.PreferImplicitTry},
|
||||
{Credo.Check.Readability.RedundantBlankLines},
|
||||
{Credo.Check.Readability.StringSigils},
|
||||
{Credo.Check.Readability.TrailingBlankLine},
|
||||
{Credo.Check.Readability.TrailingWhiteSpace},
|
||||
{Credo.Check.Readability.VariableNames},
|
||||
{Credo.Check.Readability.Semicolons},
|
||||
{Credo.Check.Readability.SpaceAfterCommas},
|
||||
|
||||
#
|
||||
## Refactoring Opportunities
|
||||
#
|
||||
{Credo.Check.Refactor.DoubleBooleanNegation},
|
||||
{Credo.Check.Refactor.CondStatements, false},
|
||||
# TODO(Connor) should probably turn this back on..
|
||||
{Credo.Check.Refactor.CyclomaticComplexity, false},
|
||||
{Credo.Check.Refactor.FunctionArity},
|
||||
{Credo.Check.Refactor.LongQuoteBlocks},
|
||||
{Credo.Check.Refactor.MatchInCondition},
|
||||
{Credo.Check.Refactor.NegatedConditionsInUnless},
|
||||
{Credo.Check.Refactor.NegatedConditionsWithElse},
|
||||
{Credo.Check.Refactor.Nesting, false},
|
||||
{Credo.Check.Refactor.PipeChainStart, false},
|
||||
{Credo.Check.Refactor.UnlessWithElse},
|
||||
|
||||
#
|
||||
## Warnings
|
||||
#
|
||||
{Credo.Check.Warning.BoolOperationOnSameValues},
|
||||
{Credo.Check.Warning.ExpensiveEmptyEnumCheck},
|
||||
{Credo.Check.Warning.IExPry},
|
||||
{Credo.Check.Warning.IoInspect},
|
||||
{Credo.Check.Warning.LazyLogging},
|
||||
{Credo.Check.Warning.OperationOnSameValues},
|
||||
{Credo.Check.Warning.OperationWithConstantResult},
|
||||
{Credo.Check.Warning.UnusedEnumOperation},
|
||||
{Credo.Check.Warning.UnusedFileOperation},
|
||||
{Credo.Check.Warning.UnusedKeywordOperation},
|
||||
{Credo.Check.Warning.UnusedListOperation},
|
||||
{Credo.Check.Warning.UnusedPathOperation},
|
||||
{Credo.Check.Warning.UnusedRegexOperation},
|
||||
{Credo.Check.Warning.UnusedStringOperation},
|
||||
{Credo.Check.Warning.UnusedTupleOperation},
|
||||
{Credo.Check.Warning.RaiseInsideRescue},
|
||||
|
||||
#
|
||||
# Controversial and experimental checks (opt-in, just remove `, false`)
|
||||
#
|
||||
{Credo.Check.Refactor.ABCSize, false},
|
||||
{Credo.Check.Refactor.AppendSingleItem, false},
|
||||
{Credo.Check.Refactor.VariableRebinding, false},
|
||||
{Credo.Check.Warning.MapGetUnsafePass, false},
|
||||
{Credo.Check.Consistency.MultiAliasImportRequireUse, false},
|
||||
|
||||
#
|
||||
# Deprecated checks (these will be deleted after a grace period)
|
||||
#
|
||||
{Credo.Check.Readability.Specs, false}
|
||||
|
||||
#
|
||||
# Custom checks can be created using `mix credo.gen.check`.
|
||||
#
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
# Used by "mix format"
|
||||
known_formatted_files =
|
||||
File.read!("formatted_files")
|
||||
|> String.split("\n")
|
||||
|> Enum.map(&String.trim(&1))
|
||||
|> List.delete("")
|
||||
|
||||
[inputs: ["mix.exs", ".formatter.exs"] ++ known_formatted_files]
|
|
@ -35,11 +35,6 @@ erl_crash.dump
|
|||
*.js
|
||||
/release-*
|
||||
/node_modules
|
||||
/farmbot_ext
|
||||
/farmbot_core
|
||||
/farmbot_os
|
||||
/farmbot_celery_script
|
||||
/farmbot_firmware
|
||||
|
||||
# Various env vars.
|
||||
.env
|
||||
|
@ -53,7 +48,6 @@ upload.sh
|
|||
artifacts
|
||||
RELEASE_NOTES
|
||||
*.hex
|
||||
!/priv/eeprom_clear.ino.hex
|
||||
# lagger can't be disabled.
|
||||
/log
|
||||
.make_state
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[submodule "c_src/farmbot-arduino-firmware"]
|
||||
path = c_src/farmbot-arduino-firmware
|
||||
url = https://github.com/FarmBot/farmbot-arduino-firmware.git
|
||||
[submodule "farmbot_core/c_src/farmbot-arduino-firmware"]
|
||||
path = farmbot_core/c_src/farmbot-arduino-firmware
|
||||
url = git@github.com:Farmbot/Farmbot-Arduino-Firmware.git
|
||||
ignore = dirty
|
||||
|
|
17
.iex.exs
17
.iex.exs
|
@ -1,17 +0,0 @@
|
|||
alias Farmbot.System.{
|
||||
ConfigStorage
|
||||
}
|
||||
|
||||
alias Farmbot.Asset
|
||||
alias Farmbot.Asset.{
|
||||
Device,
|
||||
FarmEvent,
|
||||
GenericPointer,
|
||||
Peripheral,
|
||||
Point,
|
||||
Regimen,
|
||||
Sensor,
|
||||
Sequence,
|
||||
ToolSlot,
|
||||
Tool
|
||||
}
|
|
@ -1,2 +1,2 @@
|
|||
erlang 21.2.4
|
||||
elixir 1.8.1-otp-21
|
||||
elixir 1.8.1-otp-21
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
# Changelog
|
||||
|
||||
# 8.0.0
|
||||
* Reorganize project structure
|
||||
|
||||
# 7.0.3
|
||||
* Update to AMQP to disable `heartbeat` timeouts
|
||||
|
||||
|
|
115
Makefile
115
Makefile
|
@ -1,82 +1,65 @@
|
|||
ALL :=
|
||||
CLEAN :=
|
||||
.PHONY: all clean
|
||||
.DEFAULT_GOAL: all
|
||||
|
||||
PREFIX = $(MIX_COMPILE_PATH)/../priv
|
||||
BUILD = $(MIX_COMPILE_PATH)/../obj
|
||||
MIX_ENV := $(MIX_ENV)
|
||||
MIX_TARGET := $(MIX_TARGET)
|
||||
SLACK_CHANNEL := $(SLACK_CHANNEL)
|
||||
|
||||
# Set Erlang-specific compile and linker flags
|
||||
ERL_CFLAGS ?= -I$(ERL_EI_INCLUDE_DIR)
|
||||
ERL_LDFLAGS ?= -L$(ERL_EI_LIBDIR) -lei
|
||||
|
||||
CFLAGS += -fPIC --std=c11
|
||||
LDFLAGS += -fPIC -shared
|
||||
|
||||
ifeq ($(ERL_EI_INCLUDE_DIR),)
|
||||
$(warning ERL_EI_INCLUDE_DIR not set. Invoke via mix)
|
||||
ifeq ($(MIX_ENV),)
|
||||
MIX_ENV := dev
|
||||
endif
|
||||
|
||||
ESQLITE_SRC = $(MIX_DEPS_PATH)/esqlite/c_src
|
||||
ESQLITE_BUILD = $(MIX_BUILD_PATH)/lib/esqlite/obj
|
||||
ESQLITE_PREFIX = $(MIX_BUILD_PATH)/lib/esqlite/priv
|
||||
ifeq ($(MIX_TARGET),)
|
||||
MIX_TARGET := host
|
||||
endif
|
||||
|
||||
.PHONY: fbos_arduino_firmware fbos_clean_arduino_firmware all clean
|
||||
all: help
|
||||
|
||||
SQLITE_CFLAGS := -DSQLITE_THREADSAFE=1 \
|
||||
-DSQLITE_USE_URI \
|
||||
-DSQLITE_ENABLE_FTS3 \
|
||||
-DSQLITE_ENABLE_FTS3_PARENTHESIS
|
||||
help:
|
||||
@echo "no"
|
||||
|
||||
all: $(PREFIX) \
|
||||
$(BUILD) \
|
||||
$(PREFIX)/build_calendar.so \
|
||||
$(ESQLITE_BUILD) \
|
||||
$(ESQLITE_PREFIX) \
|
||||
$(ESQLITE_PREFIX)/esqlite3_nif.so
|
||||
farmbot_core_clean:
|
||||
cd farmbot_core && \
|
||||
make clean && \
|
||||
rm -rf priv/*.hex &&\
|
||||
rm -rf priv/*.so &&\
|
||||
rm -rf ./.*.sqlite3 &&\
|
||||
rm -rf _build deps
|
||||
|
||||
clean:
|
||||
$(RM) $(PREFIX)/*.so
|
||||
$(RM) $(ESQLITE_PREFIX)/*.so
|
||||
farmbot_ext_clean:
|
||||
cd farmbot_ext && \
|
||||
rm -rf ./.*.sqlite3 &&\
|
||||
rm -rf _build deps
|
||||
|
||||
## ARDUINO FIRMWARE
|
||||
farmbot_os_clean:
|
||||
cd farmbot_os && \
|
||||
rm -rf _build deps
|
||||
|
||||
fbos_arduino_firmware:
|
||||
cd c_src/farmbot-arduino-firmware && make all BUILD_DIR=$(PWD)/_build FBARDUINO_FIRMWARE_SRC_DIR=$(PWD)/c_src/farmbot-arduino-firmware/src BIN_DIR=$(PWD)/priv
|
||||
clean: farmbot_core_clean farmbot_ext_clean farmbot_os_clean
|
||||
|
||||
fbos_clean_arduino_firmware:
|
||||
cd c_src/farmbot-arduino-firmware && make clean BUILD_DIR=$(PWD)/_build FBARDUINO_FIRMWARE_SRC_DIR=$(PWD)/c_src/farmbot-arduino-firmware/src BIN_DIR=$(PWD)/priv
|
||||
farmbot_core_test:
|
||||
cd farmbot_core && \
|
||||
MIX_ENV=test mix deps.get && \
|
||||
MIX_ENV=test mix ecto.migrate && \
|
||||
MIX_ENV=test mix compile
|
||||
|
||||
## ESQLITE NIF HACK
|
||||
farmbot_ext_test:
|
||||
cd farmbot_ext && \
|
||||
MIX_ENV=test SKIP_ARDUINO_BUILD=1 mix deps.get && \
|
||||
MIX_ENV=test SKIP_ARDUINO_BUILD=1 mix ecto.migrate && \
|
||||
MIX_ENV=test SKIP_ARDUINO_BUILD=1 mix compile
|
||||
|
||||
$(ESQLITE_PREFIX)/esqlite3_nif.so: $(ESQLITE_BUILD)/sqlite3.o $(ESQLITE_BUILD)/queue.o $(ESQLITE_BUILD)/esqlite3_nif.o
|
||||
$(CC) -o $@ $(ERL_LDFLAGS) $(LDFLAGS) $^
|
||||
farmbot_os_test:
|
||||
cd farmbot_os && \
|
||||
MIX_ENV=test SKIP_ARDUINO_BUILD=1 mix deps.get && \
|
||||
MIX_ENV=test SKIP_ARDUINO_BUILD=1 mix compile
|
||||
|
||||
$(ESQLITE_BUILD)/esqlite3_nif.o: $(ESQLITE_SRC)/esqlite3_nif.c
|
||||
$(CC) -c $(ERL_CFLAGS) $(CFLAGS) $(SQLITE_CFLAGS) -o $@ $<
|
||||
test: farmbot_core_test farmbot_ext_test farmbot_os_test
|
||||
|
||||
$(ESQLITE_BUILD)/queue.o: $(ESQLITE_SRC)/queue.c
|
||||
$(CC) -c $(ERL_CFLAGS) $(CFLAGS) $(SQLITE_CFLAGS) -o $@ $<
|
||||
farmbot_os_firmware:
|
||||
cd farmbot_os && \
|
||||
MIX_ENV=$(MIX_ENV) MIX_TARGET=$(MIX_TARGET) mix do deps.get, firmware
|
||||
|
||||
$(ESQLITE_BUILD)/sqlite3.o: $(ESQLITE_SRC)/sqlite3.c
|
||||
$(CC) -c $(CFLAGS) $(SQLITE_CFLAGS) -o $@ $<
|
||||
|
||||
## BUILD CALENDAR NIF
|
||||
|
||||
$(PREFIX)/build_calendar.so: $(BUILD)/build_calendar.o
|
||||
$(CC) -o $@ $(ERL_LDFLAGS) $(LDFLAGS) $^
|
||||
|
||||
$(BUILD)/build_calendar.o: c_src/build_calendar/build_calendar.c
|
||||
$(CC) -c $(ERL_CFLAGS) $(CFLAGS) -o $@ $<
|
||||
|
||||
## DIRECTORIES
|
||||
|
||||
$(PREFIX):
|
||||
mkdir -p $(PREFIX)
|
||||
|
||||
$(BUILD):
|
||||
mkdir -p $(BUILD)
|
||||
|
||||
$(ESQLITE_BUILD):
|
||||
mkdir -p $(ESQLITE_BUILD)
|
||||
|
||||
$(ESQLITE_PREFIX):
|
||||
mkdir -p $(ESQLITE_PREFIX)
|
||||
farmbot_os_firmware_slack: farmbot_os_firmware
|
||||
cd farmbot_os && \
|
||||
MIX_ENV=$(MIX_ENV) MIX_TARGET=$(MIX_TARGET) mix farmbot.firmware.slack --channels $(SLACK_CHANNEL)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Build status
|
||||
| Master Build Status | Staging Build Status | Beta Build Status |
|
||||
| :---: | :---: | :---: |
|
||||
| [![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) | [![Beta Build Status](https://circleci.com/gh/FarmBot/farmbot_os/tree/beta.svg?style=svg)](https://circleci.com/gh/FarmBot/farmbot_os/tree/beta)|
|
||||
| [![Master Build Status](https://circleci.com/gh/FarmBot-Labs/farmbot_os/tree/master.svg?style=svg)](https://circleci.com/gh/FarmBot-Labs/farmbot_os/tree/master) | [![Staging Build Status](https://circleci.com/gh/FarmBot-Labs/farmbot_os/tree/staging.svg?style=svg)](https://circleci.com/gh/FarmBot-Labs/farmbot_os/tree/staging) | [![Beta Build Status](https://circleci.com/gh/FarmBot-Labs/farmbot_os/tree/beta.svg?style=svg)](https://circleci.com/gh/FarmBot-Labs/farmbot_os/tree/beta)|
|
||||
---
|
||||
|
||||
# FarmBot OS
|
||||
|
@ -47,7 +47,7 @@ _Refer to the [software documentation Configurator page](https://software.farm.b
|
|||
## Problems?
|
||||
|
||||
See the [FAQ](docs/FAQ.md)
|
||||
If your problem isn't solved there please file an issue on [Github](https://github.com/FarmBot/farmbot_os/issues/new)
|
||||
If your problem isn't solved there please file an issue on [Github](https://github.com/FarmBot-Labs/farmbot_os/issues/new)
|
||||
|
||||
## Security Concerns?
|
||||
|
||||
|
@ -55,5 +55,5 @@ We take security seriously and value the input of independent researchers. Pleas
|
|||
|
||||
## Want to Help?
|
||||
|
||||
[Low Hanging Fruit](https://github.com/FarmBot/farmbot_os/search?utf8=%E2%9C%93&q=TODO)
|
||||
[Low Hanging Fruit](https://github.com/FarmBot-Labs/farmbot_os/search?utf8=%E2%9C%93&q=TODO)
|
||||
[Development](CONTRIBUTING.md)
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
# FarmbotNG
|
||||
Restructure of FarmbotOS to fix network and log errors.
|
||||
|
||||
# Things that still need migrating
|
||||
* CeleryScript
|
||||
* Scheduling
|
||||
* Integration with `farmbot_core` somehow. (`@behaviour` maybe?)
|
||||
* Farmware
|
||||
|
||||
* Error handling.
|
||||
* Avoid factory resetting at all costs
|
||||
* Where to put factory reset code? `farmbot_os` i guess?
|
||||
* how to handle lower deps crashing?
|
||||
* http syncing
|
||||
|
||||
# Things that have been migrated
|
||||
* asset storage -> `farmbot_core`
|
||||
* farmbot_firmware -> `farmbot_core`
|
||||
* logging
|
||||
* storage -> `farmbot_core`
|
||||
* uploading -> `farmbot_ext`
|
||||
* bot state management (partially)
|
||||
* global state -> `farmbot_core`
|
||||
* real time updating -> `farmbot_ext`
|
||||
* configuration (partially) -> `farmbot_core`
|
||||
* farm_events -> `farmbot_core`
|
||||
* regimens -> `farmbot_core`
|
||||
* authorization -> `farmbot_ext`
|
||||
* amqp -> `farmbot_ext`
|
||||
* http client -> `farmbot_ext`
|
||||
* auto sync messages -> `farmbot_ext`
|
||||
* Easter Eggs -> `farmbot_os` might move to `farmbot_ext`
|
||||
* OTA Updates -> `farmbot_os`
|
||||
* Networking -> `farmbot_os`
|
||||
* System things -> `farmbot_os`
|
||||
* System info -> `farmbot_os`
|
||||
* Asset registry
|
||||
* pin bindings -> `farmbot_core`
|
||||
* led system -> `farmbot_os` + `farmbot_core`* C
|
||||
* CI
|
||||
|
||||
# Things i am unsure about
|
||||
* CeleryScript - Has both network _and_ core requirements.
|
||||
* Farmware - Same
|
||||
* database migrations might have been borked/need attention for upgrading production devices.
|
||||
* Some early logs may need to be cleaned up.
|
||||
* CI
|
|
@ -1,90 +0,0 @@
|
|||
use Mix.Config
|
||||
|
||||
# Mix configs.
|
||||
target = Mix.target()
|
||||
env = Mix.env()
|
||||
|
||||
config :logger, [
|
||||
utc_log: true,
|
||||
handle_otp_reports: true,
|
||||
handle_sasl_reports: true,
|
||||
backends: [:console]
|
||||
]
|
||||
|
||||
# Stop lager redirecting :error_logger messages
|
||||
config :lager, :error_logger_redirect, false
|
||||
|
||||
# Stop lager removing Logger's :error_logger handler
|
||||
config :lager, :error_logger_whitelist, []
|
||||
|
||||
# Stop lager writing a crash log
|
||||
config :lager, :crash_log, false
|
||||
|
||||
# Use LagerLogger as lager's only handler.
|
||||
config :lager, :handlers, []
|
||||
|
||||
config :elixir, ansi_enabled: true
|
||||
config :iex, :colors, enabled: true
|
||||
|
||||
# config :ssl, protocol_version: :"tlsv1.2"
|
||||
|
||||
config :farmbot, farm_event_debug_log: false
|
||||
|
||||
# Configure your our system.
|
||||
# Default implementation needs no special stuff.
|
||||
# See Farmbot.System.Supervisor and Farmbot.System.Init for details.
|
||||
config :farmbot, :init, []
|
||||
|
||||
# Transports.
|
||||
# See Farmbot.BotState.Transport for details.
|
||||
config :farmbot, :transport, []
|
||||
|
||||
# Configure Farmbot Behaviours.
|
||||
config :farmbot, :behaviour,
|
||||
authorization: Farmbot.Bootstrap.Authorization,
|
||||
firmware_handler: Farmbot.Firmware.StubHandler,
|
||||
http_adapter: Farmbot.HTTP.HTTPoisonAdapter,
|
||||
pin_binding_handler: Farmbot.PinBinding.StubHandler,
|
||||
json_parser: Farmbot.JSON.JasonParser,
|
||||
leds_handler: Farmbot.Leds.StubHandler
|
||||
|
||||
config :farmbot, :farmware,
|
||||
first_part_farmware_manifest_url: "https://raw.githubusercontent.com/FarmBot-Labs/farmware_manifests/master/manifest.json"
|
||||
|
||||
config :farmbot, :builtins,
|
||||
pin_binding: [
|
||||
emergency_lock: -1,
|
||||
emergency_unlock: -2,
|
||||
]
|
||||
|
||||
config :farmbot,
|
||||
expected_fw_versions: ["6.4.2.F", "6.4.2.R", "6.4.2.G"],
|
||||
default_server: "https://my.farm.bot",
|
||||
default_currently_on_beta: String.contains?(to_string(:os.cmd('git rev-parse --abbrev-ref HEAD')), "beta"),
|
||||
firmware_io_logs: false,
|
||||
default_farmware_tools_release_url: "https://api.github.com/repos/FarmBot-Labs/farmware-tools/releases/11015343",
|
||||
default_ntp_server_1: "0.pool.ntp.org",
|
||||
default_ntp_server_2: "1.pool.ntp.org",
|
||||
default_dns_name: "nerves-project.org"
|
||||
|
||||
global_overlay_dir = "rootfs_overlay"
|
||||
|
||||
config :nerves, :firmware,
|
||||
rootfs_overlay: [global_overlay_dir],
|
||||
provisioning: :nerves_hub
|
||||
|
||||
case target do
|
||||
:host ->
|
||||
import_config("host/#{env}.exs")
|
||||
|
||||
_ ->
|
||||
custom_rootfs_overlay_dir = "config/target/rootfs_overlay_#{target}"
|
||||
import_config("target/#{env}.exs")
|
||||
if File.exists?("config/target/#{target}.exs"),
|
||||
do: import_config("target/#{target}.exs")
|
||||
|
||||
if File.exists?(custom_rootfs_overlay_dir),
|
||||
do: config :nerves, :firmware,
|
||||
rootfs_overlay: [global_overlay_dir, custom_rootfs_overlay_dir],
|
||||
provisioning: :nerves_hub
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
use Mix.Config
|
||||
|
||||
config :farmbot, :authorization,
|
||||
email: "travis@travis.org",
|
||||
password: "password123",
|
||||
server: "https://staging.farmbot.io"
|
|
@ -1,52 +0,0 @@
|
|||
use Mix.Config
|
||||
|
||||
unless File.exists?("config/host/auth_secret.exs") do
|
||||
Mix.raise(
|
||||
"You need to configure your dev environment. See `config/host/auth_secret_template.exs` for an example.\r\n"
|
||||
)
|
||||
end
|
||||
|
||||
import_config("auth_secret.exs")
|
||||
|
||||
config :farmbot, data_path: "tmp/"
|
||||
|
||||
# Configure your our system.
|
||||
# Default implementation needs no special stuff.
|
||||
config :farmbot, :init, [
|
||||
Farmbot.Host.Bootstrap.Configurator,
|
||||
Farmbot.System.Debug
|
||||
]
|
||||
|
||||
# Transports.
|
||||
config :farmbot, :transport, [
|
||||
Farmbot.BotState.Transport.AMQP,
|
||||
Farmbot.BotState.Transport.HTTP,
|
||||
Farmbot.BotState.Transport.Registry,
|
||||
]
|
||||
|
||||
config :farmbot, ecto_repos: [Farmbot.Repo, Farmbot.System.ConfigStorage]
|
||||
|
||||
config :farmbot, Farmbot.Repo,
|
||||
adapter: Sqlite.Ecto2,
|
||||
loggers: [],
|
||||
database: "tmp/#{Farmbot.Repo}_dev.sqlite3"
|
||||
|
||||
config :farmbot, Farmbot.System.ConfigStorage,
|
||||
adapter: Sqlite.Ecto2,
|
||||
loggers: [],
|
||||
database: "tmp/#{Farmbot.System.ConfigStorage}_dev.sqlite3"
|
||||
|
||||
config :farmbot, :farmware, first_part_farmware_manifest_url: nil
|
||||
config :farmbot, default_server: "https://staging.farm.bot"
|
||||
|
||||
# Configure Farmbot Behaviours.
|
||||
# Default Authorization behaviour.
|
||||
# SystemTasks for host mode.
|
||||
config :farmbot, :behaviour, [
|
||||
authorization: Farmbot.Bootstrap.Authorization,
|
||||
system_tasks: Farmbot.Host.SystemTasks,
|
||||
nerves_hub_handler: Farmbot.Host.NervesHubHandler,
|
||||
# firmware_handler: Farmbot.Firmware.UartHandler
|
||||
]
|
||||
|
||||
config :farmbot, :uart_handler, tty: "/dev/ttyACM0"
|
|
@ -1,2 +0,0 @@
|
|||
use Mix.Config
|
||||
import_config "dev.exs"
|
|
@ -1,46 +0,0 @@
|
|||
use Mix.Config
|
||||
|
||||
cond do
|
||||
System.get_env("CIRCLECI") ->
|
||||
Mix.shell.info [:green, "Using circle ci config."]
|
||||
import_config("auth_secret_ci.exs")
|
||||
File.exists?("config/host/auth_secret_test.exs") ->
|
||||
import_config("auth_secret_test.exs")
|
||||
true ->
|
||||
Mix.raise("You need to configure your test environment.\r\n")
|
||||
end
|
||||
|
||||
config :farmbot, data_path: "test_tmp/"
|
||||
|
||||
config :farmbot, :init, [
|
||||
Farmbot.Host.Bootstrap.Configurator,
|
||||
]
|
||||
|
||||
# Transports.
|
||||
config :farmbot, :transport, [
|
||||
Farmbot.BotState.Transport.Test,
|
||||
Farmbot.BotState.Transport.Registry,
|
||||
]
|
||||
|
||||
config :farmbot, :farmware, first_part_farmware_manifest_url: nil
|
||||
|
||||
config :farmbot, :behaviour,
|
||||
authorization: Farmbot.Bootstrap.Authorization,
|
||||
system_tasks: Farmbot.Test.SystemTasks,
|
||||
update_handler: FarmbotTestSupport.TestUpdateHandler,
|
||||
nerves_hub_handler: Farmbot.Host.NervesHubHandler
|
||||
|
||||
config :farmbot, Farmbot.Repo, [
|
||||
adapter: Sqlite.Ecto2,
|
||||
database: "test_tmp/farmbot_repo_test",
|
||||
priv: "priv/repo",
|
||||
loggers: []
|
||||
]
|
||||
|
||||
config :farmbot, Farmbot.System.ConfigStorage, [
|
||||
adapter: Sqlite.Ecto2,
|
||||
database: "test_tmp/farmbot_config_storage_test",
|
||||
loggers: []
|
||||
]
|
||||
|
||||
config :farmbot, ecto_repos: [Farmbot.Repo, Farmbot.System.ConfigStorage]
|
|
@ -1,9 +0,0 @@
|
|||
use Mix.Config
|
||||
|
||||
config :nerves_hub,
|
||||
client: Farmbot.System.NervesHubClient,
|
||||
public_keys: [File.read!("priv/staging.pub"), File.read!("priv/prod.pub")]
|
||||
|
||||
config :nerves_hub, NervesHub.Socket, [
|
||||
reconnect_interval: 60_000,
|
||||
]
|
|
@ -1,103 +0,0 @@
|
|||
use Mix.Config
|
||||
|
||||
config :farmbot, data_path: "/root"
|
||||
|
||||
# Disable tzdata autoupdates because it tries to dl the update file
|
||||
# Before we have network or ntp.
|
||||
config :tzdata, :autoupdate, :disabled
|
||||
|
||||
config :farmbot, Farmbot.Repo,
|
||||
adapter: Sqlite.Ecto2,
|
||||
loggers: [],
|
||||
database: "/root/repo-#{Mix.env()}.sqlite3"
|
||||
|
||||
config :farmbot, Farmbot.System.ConfigStorage,
|
||||
adapter: Sqlite.Ecto2,
|
||||
loggers: [],
|
||||
database: "/root/config-#{Mix.env()}.sqlite3"
|
||||
|
||||
config :farmbot, ecto_repos: [Farmbot.Repo, Farmbot.System.ConfigStorage]
|
||||
|
||||
config :logger, LoggerBackendSqlite, [
|
||||
database: "/root/debug_logs.sqlite3",
|
||||
max_logs: 10000
|
||||
]
|
||||
|
||||
# Configure your our init system.
|
||||
config :farmbot, :init, [
|
||||
Farmbot.Target.Leds.AleHandler,
|
||||
|
||||
# Autodetects if a Arduino is plugged in and configures accordingly.
|
||||
Farmbot.Firmware.UartHandler.AutoDetector,
|
||||
|
||||
# Allows for first boot configuration.
|
||||
Farmbot.Target.Bootstrap.Configurator,
|
||||
|
||||
# Handles OTA updates from NervesHub
|
||||
Farmbot.System.NervesHubClient,
|
||||
|
||||
# Start up Network
|
||||
Farmbot.Target.Network,
|
||||
|
||||
# SSH Console.
|
||||
Farmbot.Target.SSHConsole,
|
||||
|
||||
# Wait for DNS resolution
|
||||
Farmbot.Target.Network.DnsTask,
|
||||
|
||||
# Stops the disk from getting full.
|
||||
Farmbot.Target.Network.TzdataTask,
|
||||
|
||||
# Reports Disk usage every 60 seconds.
|
||||
Farmbot.Target.DiskUsageWorker,
|
||||
|
||||
# Reports Memory usage every 60 seconds.
|
||||
Farmbot.Target.MemoryUsageWorker,
|
||||
|
||||
# Reports SOC temperature every 60 seconds.
|
||||
Farmbot.Target.SocTempWorker,
|
||||
|
||||
# Reports Uptime every 60 seconds.
|
||||
Farmbot.Target.UptimeWorker,
|
||||
|
||||
# Reports Wifi info to BotState.
|
||||
Farmbot.Target.Network.InfoSupervisor,
|
||||
|
||||
# Debug stuff
|
||||
Farmbot.System.Debug,
|
||||
Farmbot.Target.Uevent.Supervisor,
|
||||
]
|
||||
|
||||
config :farmbot, :transport, [
|
||||
Farmbot.BotState.Transport.AMQP,
|
||||
Farmbot.BotState.Transport.HTTP,
|
||||
Farmbot.BotState.Transport.Registry,
|
||||
]
|
||||
|
||||
# Configure Farmbot Behaviours.
|
||||
config :farmbot, :behaviour,
|
||||
authorization: Farmbot.Bootstrap.Authorization,
|
||||
system_tasks: Farmbot.Target.SystemTasks,
|
||||
firmware_handler: Farmbot.Firmware.StubHandler,
|
||||
pin_binding_handler: Farmbot.Target.PinBinding.AleHandler,
|
||||
leds_handler: Farmbot.Target.Leds.AleHandler,
|
||||
nerves_hub_handler: Farmbot.System.NervesHubClient
|
||||
|
||||
local_file = Path.join(System.user_home!(), ".ssh/id_rsa.pub")
|
||||
local_key = if File.exists?(local_file), do: [File.read!(local_file)], else: []
|
||||
|
||||
config :nerves_firmware_ssh, authorized_keys: local_key
|
||||
|
||||
config :nerves_init_gadget,
|
||||
ifname: "usb0",
|
||||
address_method: :dhcpd,
|
||||
mdns_domain: "farmbot.local",
|
||||
node_name: nil,
|
||||
node_host: :mdns_domain
|
||||
|
||||
config :shoehorn,
|
||||
init: [:nerves_runtime, :nerves_init_gadget, :nerves_firmware_ssh],
|
||||
handler: Farmbot.ShoehornHandler,
|
||||
app: :farmbot
|
||||
|
||||
import_config("../nerves_hub.exs")
|
|
@ -1,90 +0,0 @@
|
|||
use Mix.Config
|
||||
|
||||
config :farmbot, data_path: "/root"
|
||||
|
||||
# Disable tzdata autoupdates because it tries to dl the update file
|
||||
# Before we have network or ntp.
|
||||
config :tzdata, :autoupdate, :disabled
|
||||
|
||||
config :farmbot, Farmbot.Repo,
|
||||
adapter: Sqlite.Ecto2,
|
||||
loggers: [],
|
||||
database: "/root/repo-#{Mix.env()}.sqlite3"
|
||||
|
||||
config :farmbot, Farmbot.System.ConfigStorage,
|
||||
adapter: Sqlite.Ecto2,
|
||||
loggers: [],
|
||||
database: "/root/config-#{Mix.env()}.sqlite3"
|
||||
|
||||
config :farmbot, ecto_repos: [Farmbot.Repo, Farmbot.System.ConfigStorage]
|
||||
|
||||
config :logger, LoggerBackendSqlite, [
|
||||
database: "/root/debug_logs.sqlite3",
|
||||
max_logs: 10000
|
||||
]
|
||||
|
||||
# Configure your our init system.
|
||||
config :farmbot, :init, [
|
||||
Farmbot.Target.Leds.AleHandler,
|
||||
|
||||
# Autodetects if a Arduino is plugged in and configures accordingly.
|
||||
Farmbot.Firmware.UartHandler.AutoDetector,
|
||||
|
||||
# Allows for first boot configuration.
|
||||
Farmbot.Target.Bootstrap.Configurator,
|
||||
|
||||
# Handles OTA updates from NervesHub
|
||||
Farmbot.System.NervesHubClient,
|
||||
|
||||
# Start up Network
|
||||
Farmbot.Target.Network,
|
||||
|
||||
# SSH Console.
|
||||
Farmbot.Target.SSHConsole,
|
||||
|
||||
# Wait for DNS resolution
|
||||
Farmbot.Target.Network.DnsTask,
|
||||
|
||||
# Stops the disk from getting full.
|
||||
Farmbot.Target.Network.TzdataTask,
|
||||
|
||||
# Reports Disk usage every 60 seconds.
|
||||
Farmbot.Target.DiskUsageWorker,
|
||||
|
||||
# Reports Memory usage every 60 seconds.
|
||||
Farmbot.Target.MemoryUsageWorker,
|
||||
|
||||
# Reports SOC temperature every 60 seconds.
|
||||
Farmbot.Target.SocTempWorker,
|
||||
|
||||
# Reports Uptime every 60 seconds.
|
||||
Farmbot.Target.UptimeWorker,
|
||||
|
||||
# Reports Wifi info to BotState.
|
||||
Farmbot.Target.Network.InfoSupervisor,
|
||||
|
||||
# Helps with hot plugging of serial devices.
|
||||
Farmbot.Target.Uevent.Supervisor,
|
||||
]
|
||||
|
||||
config :farmbot, :transport, [
|
||||
Farmbot.BotState.Transport.AMQP,
|
||||
Farmbot.BotState.Transport.HTTP,
|
||||
Farmbot.BotState.Transport.Registry,
|
||||
]
|
||||
|
||||
# Configure Farmbot Behaviours.
|
||||
config :farmbot, :behaviour,
|
||||
authorization: Farmbot.Bootstrap.Authorization,
|
||||
system_tasks: Farmbot.Target.SystemTasks,
|
||||
firmware_handler: Farmbot.Firmware.StubHandler,
|
||||
pin_binding_handler: Farmbot.Target.PinBinding.AleHandler,
|
||||
leds_handler: Farmbot.Target.Leds.AleHandler,
|
||||
nerves_hub_handler: Farmbot.System.NervesHubClient
|
||||
|
||||
config :shoehorn,
|
||||
init: [:nerves_runtime, :nerves_firmware_ssh],
|
||||
handler: Farmbot.ShoehornHandler,
|
||||
app: :farmbot
|
||||
|
||||
import_config("../nerves_hub.exs")
|
|
@ -1,7 +0,0 @@
|
|||
use Mix.Config
|
||||
|
||||
config :nerves_leds, names: [ status: "led0" ]
|
||||
config :farmbot, :gpio, status_led_on: false
|
||||
config :farmbot, :gpio, status_led_off: true
|
||||
|
||||
config :farmbot, kernel_modules: ["snd-bcm2835"]
|
|
@ -1,8 +0,0 @@
|
|||
use Mix.Config
|
||||
|
||||
config :nerves_leds, names: [ status: "led0" ]
|
||||
config :farmbot, :gpio, status_led_on: false
|
||||
config :farmbot, :gpio, status_led_off: true
|
||||
|
||||
config :farmbot, :captive_portal_address, "192.168.24.1"
|
||||
config :farmbot, kernel_modules: ["snd-bcm2835"]
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"skip_files": [
|
||||
"test/support",
|
||||
"nerves/",
|
||||
"lib/mix/"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
# Used by "mix format"
|
||||
[
|
||||
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
||||
]
|
|
@ -0,0 +1,28 @@
|
|||
# The directory Mix will write compiled artifacts to.
|
||||
/_build/
|
||||
|
||||
# If you run "mix test --cover", coverage assets end up here.
|
||||
/cover/
|
||||
|
||||
# The directory Mix downloads your dependencies sources to.
|
||||
/deps/
|
||||
|
||||
# Where 3rd-party dependencies like ExDoc output generated docs.
|
||||
/doc/
|
||||
|
||||
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||
/.fetch
|
||||
|
||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||
erl_crash.dump
|
||||
|
||||
# Also ignore archive artifacts (built via "mix archive.build").
|
||||
*.ez
|
||||
|
||||
# Ignore package tarball (built via "mix hex.build").
|
||||
farmbot_ng-*.tar
|
||||
|
||||
*.sqlite3
|
||||
*.so
|
||||
*.hex
|
||||
!priv/eeprom_clear.ino.hex
|
|
@ -0,0 +1 @@
|
|||
../.tool-versions
|
|
@ -0,0 +1,39 @@
|
|||
ALL :=
|
||||
CLEAN :=
|
||||
|
||||
ifeq ($(ERL_EI_INCLUDE_DIR),)
|
||||
|
||||
$(warning ERL_EI_INCLUDE_DIR not set. Invoke via mix)
|
||||
|
||||
else
|
||||
|
||||
ALL += fbos_build_calendar_nif
|
||||
CLEAN += fbos_clean_build_calendar_nif
|
||||
endif
|
||||
|
||||
ifeq ($(SKIP_ARDUINO_BUILD),)
|
||||
|
||||
ALL += fbos_arduino_firmware
|
||||
CLEAN += fbos_clean_arduino_firmware
|
||||
|
||||
else
|
||||
$(warning SKIP_ARDUINO_BUILD is set. No arduino assets will be built.)
|
||||
endif
|
||||
|
||||
.PHONY: $(ALL) $(CLEAN) all clean
|
||||
|
||||
all: $(ALL)
|
||||
|
||||
clean: $(CLEAN)
|
||||
|
||||
fbos_arduino_firmware:
|
||||
cd c_src/farmbot-arduino-firmware && make all BUILD_DIR=$(MAKE_CWD)/_build FBARDUINO_FIRMWARE_SRC_DIR=$(MAKE_CWD)/c_src/farmbot-arduino-firmware/src BIN_DIR=$(MAKE_CWD)/priv
|
||||
|
||||
fbos_clean_arduino_firmware:
|
||||
cd c_src/farmbot-arduino-firmware && make clean BUILD_DIR=$(MAKE_CWD)/_build FBARDUINO_FIRMWARE_SRC_DIR=$(MAKE_CWD)/c_src/farmbot-arduino-firmware/src BIN_DIR=$(MAKE_CWD)/priv
|
||||
|
||||
fbos_build_calendar_nif:
|
||||
make -f c_src/build_calendar/Makefile all ERL_EI_INCLUDE_DIR=$(ERL_EI_INCLUDE_DIR) ERL_EI_LIBDIR=$(ERL_EI_LIBDIR)
|
||||
|
||||
fbos_clean_build_calendar_nif:
|
||||
make -f c_src/build_calendar/Makefile clean ERL_EI_INCLUDE_DIR=$(ERL_EI_INCLUDE_DIR) ERL_EI_LIBDIR=$(ERL_EI_LIBDIR)
|
|
@ -0,0 +1,3 @@
|
|||
# FarmbotCore
|
||||
Core Farmbot Services.
|
||||
This includes Logging, Configuration, Asset management and Firmware.
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,28 @@
|
|||
ifeq ($(ERL_EI_INCLUDE_DIR),)
|
||||
$(error ERL_EI_INCLUDE_DIR not set. Invoke via mix)
|
||||
endif
|
||||
|
||||
# Set Erlang-specific compile and linker flags
|
||||
ERL_CFLAGS ?= -I$(ERL_EI_INCLUDE_DIR)
|
||||
ERL_LDFLAGS ?= -L$(ERL_EI_LIBDIR)
|
||||
|
||||
NIF_LDFLAGS += -fPIC -shared
|
||||
NIF_CFLAGS ?= -fPIC -O2 -Wall
|
||||
|
||||
NIF=priv/build_calendar.so
|
||||
|
||||
ifeq ($(CROSSCOMPILE),)
|
||||
ifeq ($(shell uname),Darwin)
|
||||
NIF_LDFLAGS += -undefined dynamic_lookup
|
||||
endif
|
||||
endif
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(NIF)
|
||||
|
||||
$(NIF): c_src/build_calendar/build_calendar.c
|
||||
$(CC) $(ERL_CFLAGS) $(NIF_CFLAGS) $(ERL_LDFLAGS) $(NIF_LDFLAGS) -o $@ $<
|
||||
|
||||
clean:
|
||||
$(RM) $(NIF)
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,43 @@
|
|||
use Mix.Config
|
||||
|
||||
# config :logger, [
|
||||
# utc_log: true,
|
||||
# handle_otp_reports: true,
|
||||
# handle_sasl_reports: true,
|
||||
# ]
|
||||
|
||||
# Configure Farmbot Behaviours.
|
||||
config :farmbot_core, :behaviour,
|
||||
firmware_handler: Farmbot.Firmware.StubHandler,
|
||||
leds_handler: Farmbot.Leds.StubHandler,
|
||||
pin_binding_handler: Farmbot.PinBinding.StubHandler,
|
||||
celery_script_io_layer: Farmbot.CeleryScript.StubIOLayer
|
||||
|
||||
config :farmbot_core,
|
||||
ecto_repos: [Farmbot.Config.Repo, Farmbot.Logger.Repo, Farmbot.Asset.Repo],
|
||||
expected_fw_versions: ["6.4.2.F", "6.4.2.R", "6.4.2.G"],
|
||||
default_server: "https://my.farm.bot",
|
||||
default_currently_on_beta: String.contains?(to_string(:os.cmd('git rev-parse --abbrev-ref HEAD')), "beta"),
|
||||
firmware_io_logs: false,
|
||||
farm_event_debug_log: false
|
||||
|
||||
config :farmbot_core, Farmbot.Config.Repo,
|
||||
adapter: Sqlite.Ecto2,
|
||||
loggers: [],
|
||||
database: ".#{Mix.env}_configs.sqlite3",
|
||||
priv: "priv/config",
|
||||
pool_size: 1
|
||||
|
||||
config :farmbot_core, Farmbot.Logger.Repo,
|
||||
adapter: Sqlite.Ecto2,
|
||||
loggers: [],
|
||||
database: ".#{Mix.env}_logs.sqlite3",
|
||||
priv: "priv/logger",
|
||||
pool_size: 1
|
||||
|
||||
config :farmbot_core, Farmbot.Asset.Repo,
|
||||
adapter: Sqlite.Ecto2,
|
||||
loggers: [],
|
||||
database: ".#{Mix.env}_assets.sqlite3",
|
||||
priv: "priv/asset",
|
||||
pool_size: 1
|
|
@ -0,0 +1,283 @@
|
|||
defmodule Farmbot.Asset do
|
||||
@moduledoc """
|
||||
API for inserting and retrieving assets.
|
||||
"""
|
||||
|
||||
alias Farmbot.Asset
|
||||
|
||||
alias Asset.{
|
||||
Repo,
|
||||
|
||||
Device,
|
||||
FarmEvent,
|
||||
Peripheral,
|
||||
PinBinding,
|
||||
Point,
|
||||
Regimen,
|
||||
Sensor,
|
||||
Sequence,
|
||||
Tool,
|
||||
|
||||
SyncCmd,
|
||||
PersistentRegimen
|
||||
}
|
||||
|
||||
alias Repo.Snapshot
|
||||
require Farmbot.Logger
|
||||
import Ecto.Query
|
||||
|
||||
def fragment_sync(verbosity \\ 1) do
|
||||
Farmbot.Logger.busy verbosity, "Syncing"
|
||||
Farmbot.Registry.dispatch(__MODULE__, {:sync_status, :syncing})
|
||||
all_sync_cmds = all_sync_cmds()
|
||||
|
||||
Repo.transaction fn() ->
|
||||
for cmd <- all_sync_cmds do
|
||||
apply_sync_cmd(cmd)
|
||||
end
|
||||
end
|
||||
|
||||
destroy_all_sync_cmds()
|
||||
Farmbot.Registry.dispatch(__MODULE__, {:sync_status, :synced})
|
||||
Farmbot.Logger.success verbosity, "Synced"
|
||||
:ok
|
||||
end
|
||||
|
||||
def apply_sync_cmd(cmd) do
|
||||
mod = Module.concat(["Farmbot", "Asset", cmd.kind])
|
||||
if Code.ensure_loaded?(mod) do
|
||||
Farmbot.Registry.dispatch(__MODULE__, {:sync_status, :syncing})
|
||||
old = Repo.snapshot()
|
||||
Farmbot.Logger.debug(3, "Syncing #{cmd.kind}")
|
||||
try do
|
||||
do_apply_sync_cmd(cmd)
|
||||
rescue
|
||||
e ->
|
||||
Farmbot.Logger.error(1, "Error syncing: #{mod}: #{Exception.message(e)}")
|
||||
end
|
||||
new = Repo.snapshot()
|
||||
diff = Snapshot.diff(old, new)
|
||||
dispatch_sync(diff)
|
||||
else
|
||||
Farmbot.Logger.warn(3, "Unknown module: #{mod} #{inspect(cmd)}")
|
||||
end
|
||||
destroy_sync_cmd(cmd)
|
||||
end
|
||||
|
||||
defp dispatch_sync(diff) do
|
||||
for deletion <- diff.deletions do
|
||||
Farmbot.Registry.dispatch(__MODULE__, {:deletion, deletion})
|
||||
end
|
||||
|
||||
for update <- diff.updates do
|
||||
Farmbot.Registry.dispatch(__MODULE__, {:update, update})
|
||||
end
|
||||
|
||||
for addition <- diff.additions do
|
||||
Farmbot.Registry.dispatch(__MODULE__, {:addition, addition})
|
||||
end
|
||||
|
||||
Farmbot.Registry.dispatch(__MODULE__, {:sync_status, :synced})
|
||||
end
|
||||
|
||||
# When `body` is nil, it means an object was deleted.
|
||||
def do_apply_sync_cmd(%{body: nil, remote_id: id, kind: kind}) do
|
||||
mod = Module.concat(["Farmbot", "Asset", kind])
|
||||
case Repo.get(mod, id) do
|
||||
nil ->
|
||||
:ok
|
||||
|
||||
existing ->
|
||||
Repo.delete!(existing)
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
def do_apply_sync_cmd(%{body: obj, remote_id: id, kind: kind}) do
|
||||
not_struct = strip_struct(obj)
|
||||
mod = Module.concat(["Farmbot", "Asset", kind])
|
||||
# We need to check if this object exists in the database.
|
||||
case Repo.get(mod, id) do
|
||||
# If it does not, just return the newly created object.
|
||||
nil ->
|
||||
change = mod.changeset(struct(mod, not_struct), not_struct)
|
||||
Repo.insert!(change)
|
||||
:ok
|
||||
# if there is an existing record, copy the ecto meta from the old
|
||||
# record. This allows `insert_or_update` to work properly.
|
||||
existing ->
|
||||
existing
|
||||
|> Ecto.Changeset.change(not_struct)
|
||||
|> Repo.update!()
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp strip_struct(%{__struct__: _, __meta__: _} = struct) do
|
||||
Map.from_struct(struct) |> Map.delete(:__meta__)
|
||||
end
|
||||
|
||||
defp strip_struct(already_map), do: already_map
|
||||
|
||||
@doc """
|
||||
Register a sync message from an external source.
|
||||
This is like a snippit of the changes that have happened.
|
||||
`sync_cmd`s should only be applied on `sync`ing.
|
||||
`sync_cmd`s are _not_ a source of truth for transactions that have been applied.
|
||||
Use the `Farmbot.Asset.Registry` for these types of events.
|
||||
"""
|
||||
def register_sync_cmd(remote_id, kind, body) when is_binary(kind) do
|
||||
SyncCmd.changeset(struct(SyncCmd, %{remote_id: remote_id, kind: kind, body: body}))
|
||||
|> Repo.insert!()
|
||||
end
|
||||
|
||||
@doc "Destroy all sync cmds locally."
|
||||
def destroy_all_sync_cmds do
|
||||
Repo.delete_all(SyncCmd)
|
||||
end
|
||||
|
||||
def all_sync_cmds do
|
||||
Repo.all(SyncCmd)
|
||||
end
|
||||
|
||||
def destroy_sync_cmd(%SyncCmd{} = cmd) do
|
||||
Repo.delete(cmd)
|
||||
end
|
||||
|
||||
def all_pin_bindings do
|
||||
Repo.all(PinBinding)
|
||||
end
|
||||
|
||||
@doc "Get all Persistent Regimens"
|
||||
def all_persistent_regimens do
|
||||
Repo.all(PersistentRegimen)
|
||||
end
|
||||
|
||||
def persistent_regimens(%Regimen{id: id} = _regimen) do
|
||||
Repo.all(from pr in PersistentRegimen, where: pr.regimen_id == ^id)
|
||||
end
|
||||
|
||||
def persistent_regimen(%Regimen{id: id, farm_event_id: fid} = _regimen) do
|
||||
fid || raise "Can't look up persistent regimens without a farm_event id."
|
||||
Repo.one(from pr in PersistentRegimen, where: pr.regimen_id == ^id and pr.farm_event_id == ^fid)
|
||||
end
|
||||
|
||||
@doc "Add a new Persistent Regimen."
|
||||
def add_persistent_regimen(%Regimen{id: id, farm_event_id: fid} = _regimen, time) do
|
||||
fid || raise "Can't save persistent regimens without a farm_event id."
|
||||
PersistentRegimen.changeset(struct(PersistentRegimen, %{regimen_id: id, time: time, farm_event_id: fid}))
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc "Delete a persistent_regimen based on it's regimen id and farm_event id."
|
||||
def delete_persistent_regimen(%Regimen{id: regimen_id, farm_event_id: fid} = _regimen) do
|
||||
fid || raise "cannot delete persistent_regimen without farm_event_id"
|
||||
itm = Repo.one(from pr in PersistentRegimen, where: pr.regimen_id == ^regimen_id and pr.farm_event_id == ^fid)
|
||||
if itm, do: Repo.delete(itm), else: nil
|
||||
end
|
||||
|
||||
def update_persistent_regimen_time(%Regimen{id: _regimen_id, farm_event_id: _fid} = regimen, %DateTime{} = time) do
|
||||
pr = persistent_regimen(regimen)
|
||||
if pr do
|
||||
pr = Ecto.Changeset.change pr, time: time
|
||||
Repo.update!(pr)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def clear_all_data do
|
||||
Repo.delete_all(Device)
|
||||
Repo.delete_all(FarmEvent)
|
||||
Repo.delete_all(Peripheral)
|
||||
Repo.delete_all(PinBinding)
|
||||
Repo.delete_all(Point)
|
||||
Repo.delete_all(Regimen)
|
||||
Repo.delete_all(Sensor)
|
||||
Repo.delete_all(Sequence)
|
||||
Repo.delete_all(Tool)
|
||||
Repo.delete_all(PersistentRegimen)
|
||||
Repo.delete_all(SyncCmd)
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc "Information about _this_ device."
|
||||
def device do
|
||||
case Repo.all(Device) do
|
||||
[device] -> device
|
||||
[] -> nil
|
||||
devices when is_list(devices) ->
|
||||
Repo.delete_all(Device)
|
||||
raise "There should only ever be 1 device!"
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Get a Peripheral by it's id."
|
||||
def get_peripheral_by_id(peripheral_id) do
|
||||
Repo.one(from(p in Peripheral, where: p.id == ^peripheral_id))
|
||||
end
|
||||
|
||||
@doc "Get a Sensor by it's id."
|
||||
def get_sensor_by_id(sensor_id) do
|
||||
Repo.one(from(s in Sensor, where: s.id == ^sensor_id))
|
||||
end
|
||||
|
||||
@doc "Get a Sequence by it's id."
|
||||
def get_sequence_by_id(sequence_id) do
|
||||
Repo.one(from(s in Sequence, where: s.id == ^sequence_id))
|
||||
end
|
||||
|
||||
@doc "Same as `get_sequence_by_id/1` but raises if no Sequence is found."
|
||||
def get_sequence_by_id!(sequence_id) do
|
||||
case get_sequence_by_id(sequence_id) do
|
||||
nil -> raise "Could not find sequence by id #{sequence_id}"
|
||||
%Sequence{} = seq -> seq
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Get a Point by it's id."
|
||||
def get_point_by_id(point_id) do
|
||||
Repo.one(from(p in Point, where: p.id == ^point_id))
|
||||
end
|
||||
|
||||
@doc "Get a Tool from a Point by `tool_id`."
|
||||
def get_point_from_tool(tool_id) do
|
||||
Repo.one(from(p in Point, where: p.tool_id == ^tool_id))
|
||||
end
|
||||
|
||||
@doc "Get a Tool by it's id."
|
||||
def get_tool_by_id(tool_id) do
|
||||
Repo.one(from(t in Tool, where: t.id == ^tool_id))
|
||||
end
|
||||
|
||||
@doc "Get a Regimen by it's id."
|
||||
def get_regimen_by_id(regimen_id, farm_event_id) do
|
||||
reg = Repo.one(from(r in Regimen, where: r.id == ^regimen_id))
|
||||
|
||||
if reg do
|
||||
%{reg | farm_event_id: farm_event_id}
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Same as `get_regimen_by_id/1` but raises if no Regimen is found."
|
||||
def get_regimen_by_id!(regimen_id, farm_event_id) do
|
||||
case get_regimen_by_id(regimen_id, farm_event_id) do
|
||||
nil -> raise "Could not find regimen by id #{regimen_id}"
|
||||
%Regimen{} = reg -> reg
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Fetches all regimens that use a particular sequence."
|
||||
def get_regimens_using_sequence(sequence_id) do
|
||||
uses_seq = &match?(^sequence_id, Map.fetch!(&1, :sequence_id))
|
||||
|
||||
Repo.all(Regimen)
|
||||
|> Enum.filter(&Enum.find(Map.fetch!(&1, :regimen_items), uses_seq))
|
||||
end
|
||||
|
||||
def get_farm_event_by_id(feid) do
|
||||
Repo.one(from(fe in FarmEvent, where: fe.id == ^feid))
|
||||
end
|
||||
end
|
|
@ -4,6 +4,7 @@ defmodule Farmbot.Asset.Device do
|
|||
problem probably higher up the stack.
|
||||
"""
|
||||
|
||||
alias Farmbot.Asset.Device
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
|
@ -14,7 +15,7 @@ defmodule Farmbot.Asset.Device do
|
|||
|
||||
@required_fields [:id, :name]
|
||||
|
||||
def changeset(device, params \\ %{}) do
|
||||
def changeset(%Device{} = device, params \\ %{}) do
|
||||
device
|
||||
|> cast(params, @required_fields)
|
||||
|> validate_required(@required_fields)
|
|
@ -9,29 +9,34 @@ defmodule Farmbot.Asset.FarmEvent do
|
|||
|
||||
@on_load :load_nif
|
||||
def load_nif do
|
||||
nif_file = '#{:code.priv_dir(:farmbot)}/build_calendar'
|
||||
require Elixir.Logger
|
||||
nif_file = '#{:code.priv_dir(:farmbot_core)}/build_calendar'
|
||||
|
||||
case :erlang.load_nif(nif_file, 0) do
|
||||
:ok -> :ok
|
||||
{:error, {:reload, _}} -> :ok
|
||||
{:error, reason} -> IO.warn("Failed to load nif: #{inspect(reason)}", [])
|
||||
{:error, reason} -> Elixir.Logger.warn("Failed to load nif: #{inspect(reason)}")
|
||||
end
|
||||
end
|
||||
|
||||
alias Farmbot.Repo.JSONType
|
||||
@callback schedule_event(map, DateTime.t) :: any
|
||||
|
||||
alias Farmbot.Asset.FarmEvent
|
||||
alias Farmbot.Asset.Repo.ModuleType
|
||||
alias Farmbot.EctoTypes.TermType
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
use Farmbot.Logger
|
||||
require Farmbot.Logger
|
||||
|
||||
schema "farm_events" do
|
||||
field(:start_time, :string)
|
||||
field(:end_time, :string)
|
||||
field(:repeat, :integer)
|
||||
field(:time_unit, :string)
|
||||
field(:executable_type, Farmbot.Repo.ModuleType.FarmEvent)
|
||||
field(:executable_type, ModuleType.FarmEvent)
|
||||
field(:executable_id, :integer)
|
||||
field(:calendar, JSONType)
|
||||
field(:calendar, TermType)
|
||||
end
|
||||
|
||||
@required_fields [
|
||||
|
@ -44,7 +49,7 @@ defmodule Farmbot.Asset.FarmEvent do
|
|||
:executable_id
|
||||
]
|
||||
|
||||
def changeset(farm_event, params \\ %{}) do
|
||||
def changeset(%FarmEvent{} = farm_event, params \\ %{}) do
|
||||
farm_event
|
||||
|> build_calendar
|
||||
|> cast(params, @required_fields ++ [:calendar])
|
||||
|
@ -53,16 +58,15 @@ defmodule Farmbot.Asset.FarmEvent do
|
|||
end
|
||||
|
||||
@compile {:inline, [build_calendar: 1]}
|
||||
def build_calendar(%__MODULE__{executable_type: Farmbot.Asset.Regimen} = fe),
|
||||
def build_calendar(%FarmEvent{executable_type: Farmbot.Asset.Regimen} = fe),
|
||||
do: fe
|
||||
|
||||
def build_calendar(%__MODULE__{calendar: nil} = fe),
|
||||
def build_calendar(%FarmEvent{calendar: nil} = fe),
|
||||
do: build_calendar(%{fe | calendar: []})
|
||||
|
||||
def build_calendar(%__MODULE__{time_unit: "never"} = fe),
|
||||
do: %{fe | calendar: [fe.start_time]}
|
||||
def build_calendar(%FarmEvent{time_unit: "never"} = fe), do: %{fe | calendar: [fe.start_time]}
|
||||
|
||||
def build_calendar(%__MODULE__{calendar: calendar} = fe)
|
||||
def build_calendar(%FarmEvent{calendar: calendar} = fe)
|
||||
when is_list(calendar) do
|
||||
current_time_seconds = :os.system_time(:second)
|
||||
|
||||
|
@ -71,7 +75,8 @@ defmodule Farmbot.Asset.FarmEvent do
|
|||
|> elem(1)
|
||||
|> DateTime.to_unix(:second)
|
||||
|
||||
end_time_seconds = DateTime.from_iso8601(fe.end_time) |> elem(1) |> DateTime.to_unix(:second)
|
||||
end_time_seconds =
|
||||
DateTime.from_iso8601(fe.end_time) |> elem(1) |> DateTime.to_unix(:second)
|
||||
|
||||
repeat = fe.repeat
|
||||
repeat_frequency_seconds = time_unit_to_seconds(fe.time_unit)
|
||||
|
@ -98,7 +103,7 @@ defmodule Farmbot.Asset.FarmEvent do
|
|||
repeat,
|
||||
repeat_frequency_seconds
|
||||
) do
|
||||
Logger.error(1, "Using (very) slow calendar builder!")
|
||||
Farmbot.Logger.error(1, "Using (very) slow calendar builder!")
|
||||
grace_period_cutoff_seconds = now_seconds - 60
|
||||
|
||||
Range.new(start_time_seconds, end_time_seconds)
|
|
@ -3,6 +3,7 @@ defmodule Farmbot.Asset.Peripheral do
|
|||
Peripherals are descriptors for pins/modes.
|
||||
"""
|
||||
|
||||
alias Farmbot.Asset.Peripheral
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
|
@ -14,7 +15,7 @@ defmodule Farmbot.Asset.Peripheral do
|
|||
|
||||
@required_fields [:id, :pin, :mode, :label]
|
||||
|
||||
def changeset(peripheral, params \\ %{}) do
|
||||
def changeset(%Peripheral{} = peripheral, params \\ %{}) do
|
||||
peripheral
|
||||
|> cast(params, @required_fields)
|
||||
|> validate_required(@required_fields)
|
|
@ -1,17 +1,19 @@
|
|||
defmodule Farmbot.System.ConfigStorage.PersistentRegimen do
|
||||
defmodule Farmbot.Asset.PersistentRegimen do
|
||||
@moduledoc """
|
||||
A persistent regimen is a join between a started farm event and the regimen
|
||||
it is set to operate on. These are stored in the database to persist reboots,
|
||||
crashes etc.
|
||||
"""
|
||||
|
||||
alias Farmbot.Asset.PersistentRegimen
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Farmbot.System.ConfigStorage.PersistentRegimen
|
||||
|
||||
schema "persistent_regimens" do
|
||||
field :regimen_id, :integer
|
||||
field :farm_event_id, :integer
|
||||
field :time, :utc_datetime
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@required_fields [:regimen_id, :farm_event_id, :time]
|
|
@ -1,24 +1,27 @@
|
|||
defmodule Farmbot.Asset.Point do
|
||||
@moduledoc "A Point is a location in the planting bed as denoted by X Y and Z."
|
||||
|
||||
alias Farmbot.Asset.Point
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Farmbot.Asset.Repo.ModuleType
|
||||
alias Farmbot.EctoTypes.TermType
|
||||
|
||||
schema "points" do
|
||||
field(:name, :string)
|
||||
field(:tool_id, :integer)
|
||||
field(:x, Farmbot.Repo.JSONFloatType)
|
||||
field(:y, Farmbot.Repo.JSONFloatType)
|
||||
field(:z, Farmbot.Repo.JSONFloatType)
|
||||
field(:meta, Farmbot.Repo.JSONType)
|
||||
field(:pointer_type, Farmbot.Repo.ModuleType.Point)
|
||||
field(:x, :float)
|
||||
field(:y, :float)
|
||||
field(:z, :float)
|
||||
field(:meta, TermType)
|
||||
field(:pointer_type, ModuleType.Point)
|
||||
end
|
||||
|
||||
@required_fields [:id, :name, :x, :y, :z, :meta, :pointer_type]
|
||||
@optional_fields [:tool_id]
|
||||
|
||||
def changeset(point, params \\ %{}) do
|
||||
point
|
||||
def changeset(%Point{} = point, params \\ %{}) do
|
||||
%Point{} = point
|
||||
|> cast(params, @required_fields ++ @optional_fields)
|
||||
|> validate_required(@required_fields)
|
||||
end
|
|
@ -3,7 +3,10 @@ defmodule Farmbot.Asset.Regimen do
|
|||
A Regimen is a schedule to run sequences on.
|
||||
"""
|
||||
|
||||
alias Farmbot.Repo.JSONType
|
||||
alias Farmbot.Asset.Regimen
|
||||
alias Farmbot.EctoTypes.TermType
|
||||
alias Farmbot.Regimen.NameProvider
|
||||
alias Farmbot.Regimen.Supervisor, as: RegimenSupervisor
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
@ -11,7 +14,7 @@ defmodule Farmbot.Asset.Regimen do
|
|||
schema "regimens" do
|
||||
field(:name, :string)
|
||||
field(:farm_event_id, :integer, virtual: true)
|
||||
field(:regimen_items, JSONType)
|
||||
field(:regimen_items, TermType)
|
||||
end
|
||||
|
||||
@type item :: %{
|
||||
|
@ -27,10 +30,19 @@ defmodule Farmbot.Asset.Regimen do
|
|||
|
||||
@required_fields [:id, :name, :regimen_items]
|
||||
|
||||
def changeset(farm_event, params \\ %{}) do
|
||||
farm_event
|
||||
def changeset(%Regimen{} = regimen, params \\ %{}) do
|
||||
regimen
|
||||
|> cast(params, @required_fields)
|
||||
|> validate_required(@required_fields)
|
||||
|> unique_constraint(:id)
|
||||
end
|
||||
|
||||
@behaviour Farmbot.Asset.FarmEvent
|
||||
def schedule_event(%Regimen{} = regimen, now) do
|
||||
name = NameProvider.via(regimen)
|
||||
case GenServer.whereis(name) do
|
||||
nil -> {:ok, _pid} = RegimenSupervisor.add_child(regimen, now)
|
||||
pid -> {:ok, pid}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
defmodule Farmbot.Asset.Repo.ModuleType.FarmEvent do
|
||||
@moduledoc false
|
||||
use Farmbot.Asset.Repo.ModuleType, valid_mods: ~w(Sequence Regimen)
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
defmodule Farmbot.Asset.Repo.ModuleType.Point do
|
||||
@moduledoc false
|
||||
use Farmbot.Asset.Repo.ModuleType, valid_mods: ~w(GenericPointer ToolSlot Plant)
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
defmodule Farmbot.Asset.Repo do
|
||||
@moduledoc "Repo for storing Asset data."
|
||||
require Farmbot.Logger
|
||||
alias Farmbot.Asset.Repo.Snapshot
|
||||
use Ecto.Repo,
|
||||
otp_app: :farmbot_core,
|
||||
adapter: Application.get_env(:farmbot_core, __MODULE__)[:adapter]
|
||||
|
||||
alias Farmbot.Asset.{
|
||||
Device,
|
||||
FarmEvent,
|
||||
Peripheral,
|
||||
PinBinding,
|
||||
Point,
|
||||
Regimen,
|
||||
Sensor,
|
||||
Sequence,
|
||||
Tool
|
||||
}
|
||||
|
||||
def snapshot do
|
||||
results = Farmbot.Asset.Repo.all(Device) ++
|
||||
Farmbot.Asset.Repo.all(FarmEvent) ++
|
||||
Farmbot.Asset.Repo.all(Peripheral) ++
|
||||
Farmbot.Asset.Repo.all(PinBinding) ++
|
||||
Farmbot.Asset.Repo.all(Point) ++
|
||||
Farmbot.Asset.Repo.all(Regimen) ++
|
||||
Farmbot.Asset.Repo.all(Sensor) ++
|
||||
Farmbot.Asset.Repo.all(Sequence) ++
|
||||
Farmbot.Asset.Repo.all(Tool)
|
||||
|
||||
struct(Snapshot, [data: results])
|
||||
|> Snapshot.md5()
|
||||
end
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
defmodule Farmbot.Repo.Snapshot do
|
||||
@moduledoc false
|
||||
alias Farmbot.Repo.Snapshot
|
||||
defmodule Farmbot.Asset.Repo.Snapshot do
|
||||
@moduledoc "Opaque data type. Hash of the entire Repo."
|
||||
alias Farmbot.Asset.Repo.Snapshot
|
||||
|
||||
defmodule Diff do
|
||||
@moduledoc false
|
|
@ -3,6 +3,7 @@ defmodule Farmbot.Asset.Sensor do
|
|||
Sensors are descriptors for pins/modes.
|
||||
"""
|
||||
|
||||
alias Farmbot.Asset.Sensor
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
|
@ -14,8 +15,8 @@ defmodule Farmbot.Asset.Sensor do
|
|||
|
||||
@required_fields [:id, :pin, :mode, :label]
|
||||
|
||||
def changeset(peripheral, params \\ %{}) do
|
||||
peripheral
|
||||
def changeset(%Sensor{} = sensor, params \\ %{}) do
|
||||
%Sensor{} = sensor
|
||||
|> cast(params, @required_fields)
|
||||
|> validate_required(@required_fields)
|
||||
|> unique_constraint(:id)
|
|
@ -0,0 +1,34 @@
|
|||
defmodule Farmbot.Asset.Sequence do
|
||||
@moduledoc """
|
||||
A Sequence is a list of CeleryScript nodes.
|
||||
"""
|
||||
|
||||
alias Farmbot.Asset.Sequence
|
||||
alias Farmbot.EctoTypes.TermType
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "sequences" do
|
||||
field(:name, :string)
|
||||
field(:kind, :string)
|
||||
field(:args, TermType)
|
||||
field(:body, TermType)
|
||||
end
|
||||
|
||||
@required_fields [:id, :name, :kind, :args, :body]
|
||||
|
||||
def changeset(%Sequence{} = sequence, params \\ %{}) do
|
||||
sequence
|
||||
|> cast(params, @required_fields)
|
||||
|> validate_required(@required_fields)
|
||||
|> unique_constraint(:id)
|
||||
end
|
||||
|
||||
@behaviour Farmbot.Asset.FarmEvent
|
||||
def schedule_event(%Sequence{} = sequence, _now) do
|
||||
case Farmbot.CeleryScript.schedule_sequence(sequence) do
|
||||
%{status: :crashed} = proc -> {:error, Csvm.FarmProc.get_crash_reason(proc)}
|
||||
_ -> :ok
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
defmodule Farmbot.Asset.Supervisor do
|
||||
@moduledoc false
|
||||
use Supervisor
|
||||
|
||||
def start_link(args) do
|
||||
Supervisor.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
children = [
|
||||
{Farmbot.Asset.Repo, [] },
|
||||
{Farmbot.Regimen.NameProvider, [] },
|
||||
{Farmbot.FarmEvent.Supervisor, [] },
|
||||
{Farmbot.Regimen.Supervisor, [] },
|
||||
{Farmbot.PinBinding.Supervisor, [] },
|
||||
]
|
||||
Supervisor.init(children, [strategy: :one_for_one])
|
||||
end
|
||||
end
|
|
@ -1,20 +1,21 @@
|
|||
defmodule Farmbot.System.ConfigStorage.SyncCmd do
|
||||
defmodule Farmbot.Asset.SyncCmd do
|
||||
@moduledoc "describes an update to a API resource."
|
||||
|
||||
alias Farmbot.Asset.SyncCmd
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Farmbot.Repo.JSONType
|
||||
alias Farmbot.EctoTypes.TermType
|
||||
|
||||
schema "sync_cmds" do
|
||||
field(:remote_id, :integer)
|
||||
field(:kind, :string)
|
||||
field(:body, JSONType)
|
||||
field(:body, TermType)
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@required_fields [:remote_id, :kind]
|
||||
|
||||
def changeset(%__MODULE__{} = cmd, params \\ %{}) do
|
||||
def changeset(%SyncCmd{} = cmd, params \\ %{}) do
|
||||
cmd
|
||||
|> cast(params, @required_fields)
|
||||
|> validate_required(@required_fields)
|
|
@ -1,6 +1,7 @@
|
|||
defmodule Farmbot.Asset.Tool do
|
||||
@moduledoc "A Tool is an item that lives in a ToolSlot"
|
||||
|
||||
alias Farmbot.Asset.Tool
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
|
@ -10,8 +11,8 @@ defmodule Farmbot.Asset.Tool do
|
|||
|
||||
@required_fields [:id, :name]
|
||||
|
||||
def changeset(farm_event, params \\ %{}) do
|
||||
farm_event
|
||||
def changeset(%Tool{} = tool, params \\ %{}) do
|
||||
tool
|
||||
|> cast(params, @required_fields)
|
||||
|> validate_required(@required_fields)
|
||||
|> unique_constraint(:id)
|
|
@ -0,0 +1,258 @@
|
|||
defmodule Farmbot.BotState do
|
||||
@moduledoc "Central State accumulator."
|
||||
alias Farmbot.BotState
|
||||
alias BotState.{
|
||||
McuParams,
|
||||
LocationData,
|
||||
Configuration,
|
||||
InformationalSettings,
|
||||
Pin
|
||||
}
|
||||
|
||||
defstruct [
|
||||
mcu_params: struct(McuParams),
|
||||
location_data: struct(LocationData),
|
||||
configuration: struct(Configuration),
|
||||
informational_settings: struct(InformationalSettings),
|
||||
pins: %{},
|
||||
process_info: %{farmwares: %{}},
|
||||
gpio_registry: %{},
|
||||
user_env: %{},
|
||||
jobs: %{}
|
||||
]
|
||||
|
||||
use GenStage
|
||||
|
||||
def download_progress_fun(name) do
|
||||
alias Farmbot.BotState.JobProgress
|
||||
require Farmbot.Logger
|
||||
fn(bytes, total) ->
|
||||
{do_send, prog} = cond do
|
||||
# if the total is complete spit out the bytes,
|
||||
# and put a status of complete.
|
||||
total == :complete ->
|
||||
Farmbot.Logger.success 3, "#{name} complete."
|
||||
{true, %JobProgress.Bytes{bytes: bytes, status: :complete}}
|
||||
|
||||
# if we don't know the total just spit out the bytes.
|
||||
total == nil ->
|
||||
# debug_log "#{name} - #{bytes} bytes."
|
||||
{rem(bytes, 10) == 0, %JobProgress.Bytes{bytes: bytes}}
|
||||
# if the number of bytes == the total bytes,
|
||||
# percentage side is complete.
|
||||
(div(bytes, total)) == 1 ->
|
||||
Farmbot.Logger.success 3, "#{name} complete."
|
||||
{true, %JobProgress.Percent{percent: 100, status: :complete}}
|
||||
# anything else is a percent.
|
||||
true ->
|
||||
percent = ((bytes / total) * 100) |> round()
|
||||
# Logger.busy 3, "#{name} - #{bytes}/#{total} = #{percent}%"
|
||||
{rem(percent, 10) == 0, %JobProgress.Percent{percent: percent}}
|
||||
end
|
||||
if do_send do
|
||||
Farmbot.BotState.set_job_progress(name, prog)
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Set job progress."
|
||||
def set_job_progress(name, progress) do
|
||||
GenServer.call(__MODULE__, {:set_job_progress, name, progress})
|
||||
end
|
||||
|
||||
def clear_progress_fun(name) do
|
||||
GenServer.call(__MODULE__, {:clear_progress_fun, name})
|
||||
end
|
||||
|
||||
@doc "Fetch the current state."
|
||||
def fetch do
|
||||
GenStage.call(__MODULE__, :fetch)
|
||||
end
|
||||
|
||||
def report_disk_usage(percent) when is_number(percent) do
|
||||
GenStage.call(__MODULE__, {:report_disk_usage, percent})
|
||||
end
|
||||
|
||||
def report_memory_usage(megabytes) when is_number(megabytes) do
|
||||
GenStage.call(__MODULE__, {:report_memory_usage, megabytes})
|
||||
end
|
||||
|
||||
def report_soc_temp(temp_celcius) when is_number(temp_celcius) do
|
||||
GenStage.call(__MODULE__, {:report_soc_temp, temp_celcius})
|
||||
end
|
||||
|
||||
def report_uptime(seconds) when is_number(seconds) do
|
||||
GenStage.call(__MODULE__, {:report_uptime, seconds})
|
||||
end
|
||||
|
||||
def report_wifi_level(level) when is_number(level) do
|
||||
GenStage.call(__MODULE__, {:report_wifi_level, level})
|
||||
end
|
||||
|
||||
@doc "Put FBOS into maintenance mode."
|
||||
def enter_maintenance_mode do
|
||||
GenStage.call(__MODULE__, :enter_maintenance_mode)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def start_link(args) do
|
||||
GenStage.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
end
|
||||
|
||||
@doc false
|
||||
def init([]) do
|
||||
Farmbot.Registry.subscribe()
|
||||
send(self(), :get_initial_configuration)
|
||||
send(self(), :get_initial_mcu_params)
|
||||
{:consumer, struct(BotState), [subscribe_to: [Farmbot.Firmware]]}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_call(:fetch, _from, state) do
|
||||
Farmbot.Registry.dispatch(__MODULE__, state)
|
||||
{:reply, state, [], state}
|
||||
end
|
||||
|
||||
# TODO(Connor) - Fix this to use event system.
|
||||
def handle_call({:set_job_progress, name, progress}, _from, state) do
|
||||
jobs = Map.put(state.jobs, name, progress)
|
||||
new_state = %{state | jobs: jobs}
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
end
|
||||
|
||||
# TODO(Connor) - Fix this to use event system.
|
||||
def handle_call({:clear_progress_fun, name}, _from, state) do
|
||||
jobs = Map.delete(state.jobs, name)
|
||||
new_state = %{state | jobs: jobs}
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
end
|
||||
|
||||
def handle_call({:report_disk_usage, percent}, _form, state) do
|
||||
event = {:informational_settings, %{disk_usage: percent}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
end
|
||||
|
||||
def handle_call({:memory_usage, megabytes}, _form, state) do
|
||||
event = {:informational_settings, %{memory_usage: megabytes}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
end
|
||||
|
||||
def handle_call({:report_soc_temp, temp}, _form, state) do
|
||||
event = {:informational_settings, %{soc_temp: temp}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
end
|
||||
|
||||
def handle_call({:uptime, seconds}, _form, state) do
|
||||
event = {:informational_settings, %{uptime: seconds}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
end
|
||||
|
||||
def handle_call({:report_wifi_level, level}, _form, state) do
|
||||
event = {:informational_settings, %{wifi_level: level}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
end
|
||||
|
||||
def handle_call(:enter_maintenance_mode, _form, state) do
|
||||
event = {:informational_settings, %{sync_status: :maintenance}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:reply, :ok, [], new_state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_info({Farmbot.Registry, {Farmbot.Config, {"settings", key, val}}}, state) do
|
||||
event = {:settings, %{String.to_atom(key) => val}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:noreply, [], new_state}
|
||||
end
|
||||
|
||||
def handle_info({Farmbot.Registry, {Farmbot.Asset.Repo, {:sync_status, status}}}, state) do
|
||||
event = {:informational_settings, %{sync_status: status}}
|
||||
new_state = handle_event(event, state)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:noreply, [], new_state}
|
||||
end
|
||||
|
||||
def handle_info({Farmbot.Registry, _}, state), do: {:noreply, [], state}
|
||||
|
||||
def handle_info(:get_initial_configuration, state) do
|
||||
full_config = Farmbot.Config.get_config_as_map()
|
||||
settings = full_config["settings"]
|
||||
new_state = Enum.reduce(settings, state, fn({key, val}, state) ->
|
||||
event = {:settings, %{String.to_atom(key) => val}}
|
||||
handle_event(event, state)
|
||||
end)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:noreply, [], new_state}
|
||||
end
|
||||
|
||||
def handle_info(:get_initial_mcu_params, state) do
|
||||
full_config = Farmbot.Config.get_config_as_map()
|
||||
settings = full_config["hardware_params"]
|
||||
new_state = Enum.reduce(settings, state, fn({key, val}, state) ->
|
||||
event = {:mcu_params, %{String.to_atom(key) => val}}
|
||||
handle_event(event, state)
|
||||
end)
|
||||
Farmbot.Registry.dispatch(__MODULE__, new_state)
|
||||
{:noreply, [], new_state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_events(events, _from, state) do
|
||||
state = Enum.reduce(events, state, &handle_event(&1, &2))
|
||||
Farmbot.Registry.dispatch(__MODULE__, state)
|
||||
{:noreply, [], state}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def handle_event({:informational_settings, data}, state) do
|
||||
new_data = Map.merge(state.informational_settings, data) |> Map.from_struct()
|
||||
new_informational_settings = struct(InformationalSettings, new_data)
|
||||
%{state | informational_settings: new_informational_settings}
|
||||
end
|
||||
|
||||
def handle_event({:mcu_params, data}, state) do
|
||||
new_data = Map.merge(state.mcu_params, data) |> Map.from_struct()
|
||||
new_mcu_params = struct(McuParams, new_data)
|
||||
%{state | mcu_params: new_mcu_params}
|
||||
end
|
||||
|
||||
def handle_event({:location_data, data}, state) do
|
||||
new_data = Map.merge(state.location_data, data) |> Map.from_struct()
|
||||
new_location_data = struct(LocationData, new_data)
|
||||
%{state | location_data: new_location_data}
|
||||
end
|
||||
|
||||
def handle_event({:pins, data}, state) do
|
||||
new_data = Enum.reduce(data, state.pins, fn({number, pin_state}, pins) ->
|
||||
Map.put(pins, number, struct(Pin, pin_state))
|
||||
end)
|
||||
%{state | pins: new_data}
|
||||
end
|
||||
|
||||
def handle_event({:settings, data}, state) do
|
||||
new_data = Map.merge(state.configuration, data) |> Map.from_struct()
|
||||
new_configuration = struct(Configuration, new_data)
|
||||
%{state | configuration: new_configuration}
|
||||
end
|
||||
|
||||
def handle_event(event, state) do
|
||||
IO.inspect event, label: "unhandled event"
|
||||
state
|
||||
end
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
defmodule Farmbot.BotState.Configuration do
|
||||
@moduledoc false
|
||||
defstruct [
|
||||
timezone: nil,
|
||||
sync_timeout_ms: nil,
|
||||
sequence_init_log: nil,
|
||||
sequence_complete_log: nil,
|
||||
sequence_body_log: nil,
|
||||
os_update_server_overwrite: nil,
|
||||
os_auto_update: nil,
|
||||
network_not_found_timer: nil,
|
||||
log_amqp_connected: nil,
|
||||
ignore_fbos_config: nil,
|
||||
ignore_external_logs: nil,
|
||||
fw_upgrade_migration: nil,
|
||||
first_sync: nil,
|
||||
first_party_farmware_url: nil,
|
||||
first_party_farmware: nil,
|
||||
first_boot: nil,
|
||||
firmware_output_log: nil,
|
||||
firmware_needs_first_sync: nil,
|
||||
firmware_input_log: nil,
|
||||
firmware_hardware: nil,
|
||||
email_on_estop: nil,
|
||||
disable_factory_reset: nil,
|
||||
currently_on_beta: nil,
|
||||
current_repo: nil,
|
||||
beta_opt_in: nil,
|
||||
auto_sync: nil,
|
||||
arduino_debug_messages: nil,
|
||||
api_migrated: nil
|
||||
]
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
defmodule Farmbot.BotState.InformationalSettings do
|
||||
@moduledoc false
|
||||
import Farmbot.Project
|
||||
defstruct [
|
||||
target: target(),
|
||||
env: env(),
|
||||
node_name: node(),
|
||||
controller_version: version(),
|
||||
firmware_commit: arduino_commit(),
|
||||
commit: commit(),
|
||||
soc_temp: 0, # degrees celcius
|
||||
wifi_level: nil, # decibels
|
||||
uptime: 0, # seconds
|
||||
memory_usage: 0, # megabytes
|
||||
disk_usage: 0, # percent
|
||||
firmware_version: nil,
|
||||
sync_status: :sync_now,
|
||||
last_status: :sync_now,
|
||||
locked: nil,
|
||||
cache_bust: nil,
|
||||
busy: nil
|
||||
]
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
defmodule Farmbot.BotState.LocationData do
|
||||
@moduledoc false
|
||||
defstruct [
|
||||
scaled_encoders: nil,
|
||||
raw_encoders: nil,
|
||||
position: nil
|
||||
]
|
||||
end
|
|
@ -0,0 +1,98 @@
|
|||
defmodule Farmbot.BotState.McuParams do
|
||||
@moduledoc false
|
||||
defstruct [
|
||||
:pin_guard_4_time_out,
|
||||
:pin_guard_1_active_state,
|
||||
:encoder_scaling_y,
|
||||
:movement_invert_2_endpoints_x,
|
||||
:movement_min_spd_y,
|
||||
:pin_guard_2_time_out,
|
||||
:movement_timeout_y,
|
||||
:movement_home_at_boot_y,
|
||||
:movement_home_spd_z,
|
||||
:movement_invert_endpoints_z,
|
||||
:pin_guard_1_pin_nr,
|
||||
:movement_invert_endpoints_y,
|
||||
:movement_max_spd_y,
|
||||
:movement_home_up_y,
|
||||
:encoder_missed_steps_decay_z,
|
||||
:movement_home_spd_y,
|
||||
:encoder_use_for_pos_x,
|
||||
:movement_step_per_mm_x,
|
||||
:movement_home_at_boot_z,
|
||||
:movement_steps_acc_dec_z,
|
||||
:pin_guard_5_pin_nr,
|
||||
:movement_invert_motor_z,
|
||||
:movement_max_spd_x,
|
||||
:movement_enable_endpoints_y,
|
||||
:movement_enable_endpoints_z,
|
||||
:param_config_ok,
|
||||
:movement_stop_at_home_x,
|
||||
:movement_axis_nr_steps_y,
|
||||
:pin_guard_1_time_out,
|
||||
:movement_home_at_boot_x,
|
||||
:pin_guard_2_pin_nr,
|
||||
:encoder_scaling_z,
|
||||
:param_e_stop_on_mov_err,
|
||||
:encoder_enabled_x,
|
||||
:pin_guard_2_active_state,
|
||||
:encoder_missed_steps_decay_y,
|
||||
:param_use_eeprom,
|
||||
:movement_home_up_z,
|
||||
:movement_enable_endpoints_x,
|
||||
:movement_step_per_mm_y,
|
||||
:pin_guard_3_pin_nr,
|
||||
:param_mov_nr_retry,
|
||||
:movement_stop_at_home_z,
|
||||
:pin_guard_4_active_state,
|
||||
:movement_steps_acc_dec_y,
|
||||
:movement_home_spd_x,
|
||||
:movement_keep_active_x,
|
||||
:pin_guard_3_time_out,
|
||||
:movement_keep_active_y,
|
||||
:encoder_scaling_x,
|
||||
:param_version,
|
||||
:movement_invert_2_endpoints_z,
|
||||
:encoder_missed_steps_decay_x,
|
||||
:movement_timeout_z,
|
||||
:encoder_missed_steps_max_z,
|
||||
:movement_min_spd_z,
|
||||
:encoder_enabled_y,
|
||||
:encoder_type_y,
|
||||
:movement_home_up_x,
|
||||
:pin_guard_3_active_state,
|
||||
:movement_invert_motor_x,
|
||||
:movement_keep_active_z,
|
||||
:movement_max_spd_z,
|
||||
:movement_secondary_motor_invert_x,
|
||||
:movement_stop_at_max_x,
|
||||
:movement_steps_acc_dec_x,
|
||||
:pin_guard_4_pin_nr,
|
||||
:param_test,
|
||||
:encoder_type_x,
|
||||
:movement_invert_2_endpoints_y,
|
||||
:encoder_invert_y,
|
||||
:movement_axis_nr_steps_x,
|
||||
:movement_stop_at_max_z,
|
||||
:movement_invert_endpoints_x,
|
||||
:encoder_invert_z,
|
||||
:encoder_use_for_pos_z,
|
||||
:pin_guard_5_active_state,
|
||||
:movement_step_per_mm_z,
|
||||
:encoder_enabled_z,
|
||||
:movement_secondary_motor_x,
|
||||
:pin_guard_5_time_out,
|
||||
:movement_min_spd_x,
|
||||
:encoder_type_z,
|
||||
:movement_stop_at_max_y,
|
||||
:encoder_use_for_pos_y,
|
||||
:encoder_missed_steps_max_y,
|
||||
:movement_timeout_x,
|
||||
:movement_stop_at_home_y,
|
||||
:movement_axis_nr_steps_z,
|
||||
:encoder_invert_x,
|
||||
:encoder_missed_steps_max_x,
|
||||
:movement_invert_motor_y,
|
||||
|
||||
]
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
defmodule Farmbot.BotState.Pin do
|
||||
@moduledoc false
|
||||
defstruct [:mode, :value]
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
defmodule Farmbot.CeleryScript do
|
||||
def to_ast(data) do
|
||||
Csvm.AST.decode(data)
|
||||
end
|
||||
|
||||
def execute_sequence(%Farmbot.Asset.Sequence{} = seq) do
|
||||
schedule_sequence(seq)
|
||||
|> await_sequence()
|
||||
end
|
||||
|
||||
def schedule_sequence(%Farmbot.Asset.Sequence{} = seq) do
|
||||
Csvm.queue(seq, seq.id)
|
||||
end
|
||||
|
||||
def await_sequence(ref) do
|
||||
Csvm.await(ref)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
defmodule Farmbot.CeleryScript.CsvmWrapper do
|
||||
@moduledoc false
|
||||
@io_layer Application.get_env(:farmbot_core, :behaviour)[:celery_script_io_layer]
|
||||
@io_layer || Mix.raise("No celery_script IO layer!")
|
||||
|
||||
def child_spec(opts) do
|
||||
%{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, [opts]},
|
||||
type: :worker,
|
||||
restart: :permanent,
|
||||
shutdown: 500
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
def start_link(_args) do
|
||||
Csvm.start_link([io_layer: &@io_layer.handle_io/1], name: Csvm)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
defmodule Farmbot.CeleryScript.IOLayer do
|
||||
@callback handle_io(Csvm.AST.t()) :: {:ok, Csvm.AST.t()} | :ok | {:error, String.t()}
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Farmbot.CeleryScript.StubIOLayer do
|
||||
@behaviour Farmbot.CeleryScript.IOLayer
|
||||
|
||||
def handle_io(ast) do
|
||||
IO.puts "#{ast.kind} not implemented."
|
||||
# {:error, "#{ast.kind} not implemented."}
|
||||
:ok
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
defmodule Farmbot.CeleryScript.Supervisor do
|
||||
@moduledoc false
|
||||
use Supervisor
|
||||
|
||||
def start_link(args) do
|
||||
Supervisor.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
children = [
|
||||
{Farmbot.CeleryScript.CsvmWrapper, []}
|
||||
]
|
||||
Supervisor.init(children, [strategy: :one_for_one])
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Farmbot.System.ConfigStorage.BoolValue do
|
||||
defmodule Farmbot.Config.BoolValue do
|
||||
@moduledoc false
|
||||
|
||||
use Ecto.Schema
|
|
@ -1,9 +1,9 @@
|
|||
defmodule Farmbot.System.ConfigStorage.Config do
|
||||
defmodule Farmbot.Config.Config do
|
||||
@moduledoc false
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias Farmbot.System.ConfigStorage.{Group, BoolValue, FloatValue, StringValue}
|
||||
alias Farmbot.Config.{Group, BoolValue, FloatValue, StringValue}
|
||||
|
||||
schema "configs" do
|
||||
belongs_to(:group, Group)
|
|
@ -0,0 +1,138 @@
|
|||
defmodule Farmbot.Config do
|
||||
@moduledoc "API for accessing config data."
|
||||
|
||||
alias Farmbot.Config.{
|
||||
Repo,
|
||||
Config, Group, BoolValue, FloatValue, StringValue, NetworkInterface
|
||||
}
|
||||
|
||||
import Ecto.Query, only: [from: 2]
|
||||
|
||||
@doc "Input a network config. Takes many settings as a map."
|
||||
def input_network_config!(%{} = config) do
|
||||
Farmbot.Config.destroy_all_network_configs()
|
||||
data = struct(NetworkInterface, config)
|
||||
Repo.insert!(data)
|
||||
end
|
||||
|
||||
def get_all_network_configs do
|
||||
Repo.all(NetworkInterface)
|
||||
end
|
||||
|
||||
def destroy_all_network_configs do
|
||||
Repo.delete_all(NetworkInterface)
|
||||
end
|
||||
|
||||
@doc "Please be careful with this. It uses a lot of queries."
|
||||
def get_config_as_map do
|
||||
groups = from(g in Group, select: g) |> Repo.all()
|
||||
|
||||
Map.new(groups, fn group ->
|
||||
vals = from(b in Config, where: b.group_id == ^group.id, select: b) |> Repo.all()
|
||||
|
||||
s =
|
||||
Map.new(vals, fn val ->
|
||||
[value] =
|
||||
Enum.find_value(val |> Map.from_struct(), fn {_key, _val} = f ->
|
||||
case f do
|
||||
{:bool_value_id, id} when is_number(id) ->
|
||||
Repo.all(from(v in BoolValue, where: v.id == ^id, select: v.value))
|
||||
|
||||
{:float_value_id, id} when is_number(id) ->
|
||||
Repo.all(from(v in FloatValue, where: v.id == ^id, select: v.value))
|
||||
|
||||
{:string_value_id, id} when is_number(id) ->
|
||||
Repo.all(from(v in StringValue, where: v.id == ^id, select: v.value))
|
||||
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
end)
|
||||
|
||||
{val.key, value}
|
||||
end)
|
||||
|
||||
{group.group_name, s}
|
||||
end)
|
||||
end
|
||||
|
||||
def get_config_value(type, group_name, key_name) when type in [:bool, :float, :string] do
|
||||
__MODULE__
|
||||
|> apply(:"get_#{type}_value", [group_name, key_name])
|
||||
|> Map.fetch!(:value)
|
||||
end
|
||||
|
||||
def get_config_value(type, _, _) do
|
||||
raise "Unsupported type: #{type}"
|
||||
end
|
||||
|
||||
def update_config_value(type, group_name, key_name, value) when type in [:bool, :float, :string] do
|
||||
__MODULE__
|
||||
|> apply(:"get_#{type}_value", [group_name, key_name])
|
||||
|> Ecto.Changeset.change(value: value)
|
||||
|> Repo.update!()
|
||||
|> dispatch(group_name, key_name)
|
||||
end
|
||||
|
||||
def update_config_value(type, _, _, _) do
|
||||
raise "Unsupported type: #{type}"
|
||||
end
|
||||
|
||||
def get_bool_value(group_name, key_name) do
|
||||
group_id = get_group_id(group_name)
|
||||
|
||||
case from(
|
||||
c in Config,
|
||||
where: c.group_id == ^group_id and c.key == ^key_name,
|
||||
select: c.bool_value_id
|
||||
)
|
||||
|> Repo.all() do
|
||||
[type_id] ->
|
||||
[val] = from(v in BoolValue, where: v.id == ^type_id, select: v) |> Repo.all()
|
||||
val
|
||||
|
||||
[] ->
|
||||
raise "no such key #{key_name}"
|
||||
end
|
||||
end
|
||||
|
||||
def get_float_value(group_name, key_name) do
|
||||
group_id = get_group_id(group_name)
|
||||
|
||||
[type_id] =
|
||||
from(
|
||||
c in Config,
|
||||
where: c.group_id == ^group_id and c.key == ^key_name,
|
||||
select: c.float_value_id
|
||||
)
|
||||
|> Repo.all()
|
||||
|
||||
[val] = from(v in FloatValue, where: v.id == ^type_id, select: v) |> Repo.all()
|
||||
val
|
||||
end
|
||||
|
||||
def get_string_value(group_name, key_name) do
|
||||
group_id = get_group_id(group_name)
|
||||
|
||||
[type_id] =
|
||||
from(
|
||||
c in Config,
|
||||
where: c.group_id == ^group_id and c.key == ^key_name,
|
||||
select: c.string_value_id
|
||||
)
|
||||
|> Repo.all()
|
||||
|
||||
[val] = from(v in StringValue, where: v.id == ^type_id, select: v) |> Repo.all()
|
||||
val
|
||||
end
|
||||
|
||||
defp get_group_id(group_name) do
|
||||
[group_id] = from(g in Group, where: g.group_name == ^group_name, select: g.id) |> Repo.all()
|
||||
group_id
|
||||
end
|
||||
|
||||
defp dispatch(%{value: value} = val, group, key) do
|
||||
Farmbot.Registry.dispatch(__MODULE__, {group, key, value})
|
||||
val
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Farmbot.System.ConfigStorage.FloatValue do
|
||||
defmodule Farmbot.Config.FloatValue do
|
||||
@moduledoc false
|
||||
|
||||
use Ecto.Schema
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Farmbot.System.ConfigStorage.Group do
|
||||
defmodule Farmbot.Config.Group do
|
||||
@moduledoc false
|
||||
|
||||
use Ecto.Schema
|
|
@ -1,10 +1,10 @@
|
|||
defmodule Farmbot.System.ConfigStorage.MigrationHelpers do
|
||||
defmodule Farmbot.Config.MigrationHelpers do
|
||||
@moduledoc false
|
||||
|
||||
# This is pretty bad practice, but i don't plan on really changing it at all.
|
||||
|
||||
alias Farmbot.System.ConfigStorage
|
||||
alias ConfigStorage.{Config, StringValue, BoolValue, FloatValue}
|
||||
alias Farmbot.Config
|
||||
alias Config.{Repo, Config, StringValue, BoolValue, FloatValue}
|
||||
|
||||
@auth_group_id 1
|
||||
@hw_param_group_id 2
|
||||
|
@ -35,7 +35,7 @@ defmodule Farmbot.System.ConfigStorage.MigrationHelpers do
|
|||
value.id
|
||||
)
|
||||
|> Config.changeset()
|
||||
|> ConfigStorage.insert!()
|
||||
|> Repo.insert!()
|
||||
end
|
||||
|
||||
def create_value(type, val \\ nil) do
|
||||
|
@ -47,6 +47,6 @@ defmodule Farmbot.System.ConfigStorage.MigrationHelpers do
|
|||
|> struct()
|
||||
|> Map.put(:value, val)
|
||||
|> type.changeset()
|
||||
|> ConfigStorage.insert!()
|
||||
|> Repo.insert!()
|
||||
end
|
||||
end
|
|
@ -1,9 +1,9 @@
|
|||
defmodule Farmbot.System.ConfigStorage.NetworkInterface do
|
||||
defmodule Farmbot.Config.NetworkInterface do
|
||||
@moduledoc false
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
use Farmbot.Logger
|
||||
require Farmbot.Logger
|
||||
|
||||
schema "network_interfaces" do
|
||||
field(:name, :string, null: false)
|
|
@ -0,0 +1,5 @@
|
|||
defmodule Farmbot.Config.Repo do
|
||||
@moduledoc "Repo for storing config data."
|
||||
use Ecto.Repo, otp_app: :farmbot_core,
|
||||
adapter: Application.get_env(:farmbot_core, __MODULE__)[:adapter]
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Farmbot.System.ConfigStorage.StringValue do
|
||||
defmodule Farmbot.Config.StringValue do
|
||||
@moduledoc false
|
||||
|
||||
use Ecto.Schema
|
|
@ -0,0 +1,15 @@
|
|||
defmodule Farmbot.Config.Supervisor do
|
||||
@moduledoc false
|
||||
use Supervisor
|
||||
|
||||
def start_link(args) do
|
||||
Supervisor.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
children = [
|
||||
{Farmbot.Config.Repo, []},
|
||||
]
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Farmbot.Repo.ModuleType do
|
||||
defmodule Farmbot.Asset.Repo.ModuleType do
|
||||
@moduledoc """
|
||||
Custom Ecto.Type for changing a string to a module.
|
||||
"""
|
|
@ -0,0 +1,17 @@
|
|||
defmodule Farmbot.EctoTypes.TermType do
|
||||
@moduledoc "Encodes/decodes data via the Erlang Term Format"
|
||||
@behaviour Ecto.Type
|
||||
|
||||
def type, do: :text
|
||||
|
||||
def cast(binary) when is_binary(binary) do
|
||||
{:ok, :erlang.binary_to_term(binary)}
|
||||
end
|
||||
|
||||
def cast(term) do
|
||||
{:ok, term}
|
||||
end
|
||||
|
||||
def load(binary) when is_binary(binary), do: {:ok, :erlang.binary_to_term(binary)}
|
||||
def dump(term), do: {:ok, :erlang.term_to_binary(term)}
|
||||
end
|
|
@ -18,11 +18,10 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
# credo:disable-for-this-file Credo.Check.Refactor.FunctionArity
|
||||
|
||||
use GenServer
|
||||
use Farmbot.Logger
|
||||
alias Farmbot.FarmEvent.Execution
|
||||
require Farmbot.Logger
|
||||
alias Farmbot.Asset
|
||||
alias Farmbot.Asset.{FarmEvent, Sequence, Regimen}
|
||||
alias Farmbot.Repo.Registry
|
||||
alias Farmbot.Registry
|
||||
|
||||
@checkup_time 1000
|
||||
# @checkup_time 15_000
|
||||
|
@ -35,28 +34,28 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
end
|
||||
|
||||
@doc false
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, [], name: __MODULE__)
|
||||
def start_link(args) do
|
||||
GenServer.start_link(__MODULE__, args, name: __MODULE__)
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
Registry.subscribe()
|
||||
Farmbot.Registry.subscribe()
|
||||
send(self(), :checkup)
|
||||
{:ok, struct(State)}
|
||||
end
|
||||
|
||||
def terminate(reason, _state) do
|
||||
Logger.error(1, "FarmEvent Manager terminated: #{inspect(reason)}")
|
||||
Farmbot.Logger.error(1, "FarmEvent Manager terminated: #{inspect(reason)}")
|
||||
end
|
||||
|
||||
def handle_info({Registry, :addition, FarmEvent, data}, state) do
|
||||
def handle_info({Registry, {Asset, {:addition, %FarmEvent{} = data}}}, state) do
|
||||
maybe_farm_event_log("Starting monitor on FarmEvent: #{data.id}.")
|
||||
|
||||
Map.put(state.events, data.id, data)
|
||||
|> reindex(state)
|
||||
end
|
||||
|
||||
def handle_info({Registry, :deletion, FarmEvent, data}, state) do
|
||||
def handle_info({Registry, {Asset, {:deletion, %FarmEvent{} = data}}}, state) do
|
||||
maybe_farm_event_log("Destroying monitor on FarmEvent: #{data.id}.")
|
||||
|
||||
if String.contains?(data.executable_type, "Regimen") do
|
||||
|
@ -71,7 +70,7 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
|> reindex(state)
|
||||
end
|
||||
|
||||
def handle_info({Registry, :update, FarmEvent, data}, state) do
|
||||
def handle_info({Registry, {Asset, {:update, %FarmEvent{} = data}}}, state) do
|
||||
maybe_farm_event_log("Reindexing monitor on FarmEvent: #{data.id}.")
|
||||
|
||||
if String.contains?(data.executable_type, "Regimen") do
|
||||
|
@ -89,17 +88,17 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
|> reindex(state)
|
||||
end
|
||||
|
||||
def handle_info({Registry, :deletion, Regimen, data}, state) do
|
||||
def handle_info({Registry, {Asset, {:deletion, %Regimen{} = data}}}, state) do
|
||||
Farmbot.Regimen.Supervisor.stop_all_managers(data)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({Registry, :update, Regimen, data}, state) do
|
||||
def handle_info({Registry, {Asset, {:update, %Regimen{} = data}}}, state) do
|
||||
Farmbot.Regimen.Supervisor.reindex_all_managers(data)
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({Registry, :update, Sequence, sequence}, state) do
|
||||
def handle_info({Registry, {Asset, {:update, %Sequence{} = sequence}}}, state) do
|
||||
for reg <- Farmbot.Asset.get_regimens_using_sequence(sequence.id) do
|
||||
Farmbot.Regimen.Supervisor.reindex_all_managers(reg)
|
||||
end
|
||||
|
@ -107,7 +106,7 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
{:noreply, state}
|
||||
end
|
||||
|
||||
def handle_info({Registry, _, _, _}, state) do
|
||||
def handle_info({Registry, _}, state) do
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
|
@ -122,7 +121,7 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
end
|
||||
|
||||
def handle_info({:DOWN, _, :process, _, error}, state) do
|
||||
Logger.error(1, "Farmevent checkup process died: #{inspect(error)}")
|
||||
Farmbot.Logger.error(1, "Farmevent checkup process died: #{inspect(error)}")
|
||||
timer = Process.send_after(self(), :checkup, @checkup_time)
|
||||
{:noreply, %{state | timer: timer, checkup: nil}}
|
||||
end
|
||||
|
@ -162,11 +161,14 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
# Map over the events for logging.
|
||||
# Both Sequences and Regimens have a `name` field.
|
||||
names = Enum.map(late_executables, &Map.get(elem(&1, 0), :name))
|
||||
Logger.debug(3, "Time for events: #{inspect(names)} to be scheduled.")
|
||||
Farmbot.Logger.debug(3, "Time for events: #{inspect(names)} to be scheduled.")
|
||||
schedule_events(late_executables, now)
|
||||
end
|
||||
|
||||
exit({:success, %{new | events: Map.new(all_events, fn event -> {event.id, event} end)}})
|
||||
exit(
|
||||
{:success,
|
||||
%{new | events: Map.new(all_events, fn event -> {event.id, event} end)}}
|
||||
)
|
||||
end
|
||||
|
||||
defp do_checkup(list, time, late_events \\ [], state)
|
||||
|
@ -181,7 +183,8 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
# update state.
|
||||
new_state = %{
|
||||
state
|
||||
| last_time_index: Map.put(state.last_time_index, farm_event.id, last_time)
|
||||
| last_time_index:
|
||||
Map.put(state.last_time_index, farm_event.id, last_time)
|
||||
}
|
||||
|
||||
case new_late_executable do
|
||||
|
@ -263,7 +266,9 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
event,
|
||||
_now
|
||||
) do
|
||||
maybe_farm_event_log("regimen #{event.name} (#{event.id}) should already be scheduled.")
|
||||
maybe_farm_event_log(
|
||||
"regimen #{event.name} (#{event.id}) should already be scheduled."
|
||||
)
|
||||
|
||||
{nil, last_time}
|
||||
end
|
||||
|
@ -276,9 +281,9 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
_
|
||||
) do
|
||||
maybe_farm_event_log(
|
||||
"regimen #{event.name} (#{event.id}) is not scheduled yet. (#{inspect(schedule_time)}) (#{
|
||||
inspect(Timex.now())
|
||||
})"
|
||||
"regimen #{event.name} (#{event.id}) is not scheduled yet. (#{
|
||||
inspect(schedule_time)
|
||||
}) (#{inspect(Timex.now())})"
|
||||
)
|
||||
|
||||
{nil, last_time}
|
||||
|
@ -317,7 +322,8 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
event,
|
||||
now
|
||||
) do
|
||||
{run?, next_time} = should_run_sequence?(farm_event.calendar, last_time, now)
|
||||
{run?, next_time} =
|
||||
should_run_sequence?(farm_event.calendar, last_time, now)
|
||||
|
||||
case run? do
|
||||
true -> {event, next_time}
|
||||
|
@ -352,7 +358,9 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
event,
|
||||
_now
|
||||
) do
|
||||
maybe_farm_event_log("sequence #{event.name} (#{event.id}) is not scheduled yet.")
|
||||
maybe_farm_event_log(
|
||||
"sequence #{event.name} (#{event.id}) is not scheduled yet."
|
||||
)
|
||||
|
||||
{nil, last_time}
|
||||
end
|
||||
|
@ -375,7 +383,9 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
|
||||
# if there is no last time, check if time is passed now within 60 seconds.
|
||||
defp should_run_sequence?([first_time | _], nil, now) do
|
||||
maybe_farm_event_log("Checking sequence event that hasn't run before #{first_time}")
|
||||
maybe_farm_event_log(
|
||||
"Checking sequence event that hasn't run before #{first_time}"
|
||||
)
|
||||
|
||||
# convert the first_time to a DateTime
|
||||
dt = Timex.parse!(first_time, "{ISO:Extended}")
|
||||
|
@ -434,15 +444,11 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
defp schedule_events([{executable, farm_event} | rest], now) do
|
||||
# Spawn to be non blocking here. Maybe link to this process?
|
||||
time = Timex.parse!(farm_event.start_time, "{ISO:Extended}")
|
||||
|
||||
cond do
|
||||
match?(%Regimen{}, executable) ->
|
||||
spawn(fn ->
|
||||
Execution.execute_event(executable, time)
|
||||
end)
|
||||
|
||||
spawn(Regimen, :schedule_event, [executable, time])
|
||||
match?(%Sequence{}, executable) ->
|
||||
spawn(fn -> Execution.execute_event(executable, now) end)
|
||||
spawn(Sequence, :schedule_event, [executable, time])
|
||||
end
|
||||
|
||||
# Continue enumeration.
|
||||
|
@ -452,8 +458,8 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
defp get_now(), do: Timex.now()
|
||||
|
||||
defp maybe_farm_event_log(message) do
|
||||
if Application.get_env(:farmbot, :farm_event_debug_log) do
|
||||
Logger.debug(3, message)
|
||||
if Application.get_env(:farmbot_core, :farm_event_debug_log) do
|
||||
Farmbot.Logger.debug(3, message)
|
||||
else
|
||||
:ok
|
||||
end
|
||||
|
@ -461,6 +467,6 @@ defmodule Farmbot.FarmEvent.Manager do
|
|||
|
||||
@doc "Enable or disbale debug logs for farmevents."
|
||||
def debug_logs(bool \\ true) when is_boolean(bool) do
|
||||
Application.put_env(:farmbot, :farm_event_debug_log, bool)
|
||||
Application.put_env(:farmbot_core, :farm_event_debug_log, bool)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
defmodule Farmbot.FarmEvent.Supervisor do
|
||||
@moduledoc false
|
||||
use Supervisor
|
||||
|
||||
def start_link(args) do
|
||||
Supervisor.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
children = [
|
||||
{Farmbot.FarmEvent.Manager, []}
|
||||
]
|
||||
|
||||
Supervisor.init(children, strategy: :one_for_one)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
defmodule Farmbot.Core do
|
||||
@moduledoc """
|
||||
Core Farmbot Services.
|
||||
This includes Logging, Configuration, Asset management and Firmware.
|
||||
"""
|
||||
use Application
|
||||
|
||||
def child_spec(opts) do
|
||||
%{
|
||||
id: __MODULE__,
|
||||
start: {__MODULE__, :start_link, [opts]},
|
||||
type: :worker,
|
||||
restart: :permanent,
|
||||
shutdown: 500
|
||||
}
|
||||
end
|
||||
|
||||
@doc false
|
||||
def start(_, args), do: Supervisor.start_link(__MODULE__, args, name: __MODULE__)
|
||||
|
||||
def start_link(args), do: Supervisor.start_link(__MODULE__, args, name: __MODULE__)
|
||||
|
||||
def init([]) do
|
||||
children = [
|
||||
{Farmbot.Registry, [] },
|
||||
{Farmbot.Logger.Supervisor, [] },
|
||||
{Farmbot.Config.Supervisor, [] },
|
||||
{Farmbot.Asset.Supervisor, [] },
|
||||
{Farmbot.Firmware.Supervisor, [] },
|
||||
{Farmbot.BotState, [] },
|
||||
{Farmbot.CeleryScript.Supervisor, [] },
|
||||
]
|
||||
Supervisor.init(children, [strategy: :one_for_one])
|
||||
end
|
||||
end
|
|
@ -1,12 +1,12 @@
|
|||
defmodule Farmbot.Firmware.CompletionLogs do
|
||||
@moduledoc false
|
||||
use Farmbot.Logger
|
||||
import Farmbot.System.ConfigStorage, only: [get_config_value: 3]
|
||||
require Farmbot.Logger
|
||||
import Farmbot.Config, only: [get_config_value: 3]
|
||||
alias Farmbot.Firmware.Command
|
||||
|
||||
def maybe_log_complete(%Command{fun: :move_absolute, args: [pos | _]}, {:error, _reason}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Logger.error 1, "Movement to #{inspect pos} failed."
|
||||
Farmbot.Logger.error 1, "Movement to #{inspect pos} failed."
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -21,58 +21,58 @@ defmodule Farmbot.Firmware.CompletionLogs do
|
|||
_ -> pos
|
||||
end
|
||||
end)
|
||||
Logger.success 1, "Movement to #{inspect pos} complete. (Stopped at end)"
|
||||
Farmbot.Logger.success 1, "Movement to #{inspect pos} complete. (Stopped at end)"
|
||||
else
|
||||
Logger.success 1, "Movement to #{inspect pos} complete."
|
||||
Farmbot.Logger.success 1, "Movement to #{inspect pos} complete."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(%Command{fun: :home}, {:error, _reason}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Logger.error 1, "Movement to (0, 0, 0) failed."
|
||||
Farmbot.Logger.error 1, "Movement to (0, 0, 0) failed."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(%Command{fun: :home_all}, _reply) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Logger.success 1, "Movement to (0, 0, 0) complete."
|
||||
Farmbot.Logger.success 1, "Movement to (0, 0, 0) complete."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(_command, {:error, :report_axis_home_complete_x}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Logger.success 2, "X Axis homing complete."
|
||||
Farmbot.Logger.success 2, "X Axis homing complete."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(_command, {:error, :report_axis_home_complete_y}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Logger.success 2, "Y Axis homing complete."
|
||||
Farmbot.Logger.success 2, "Y Axis homing complete."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(_command, {:error, :report_axis_home_complete_z}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Logger.success 2, "Z Axis homing complete."
|
||||
Farmbot.Logger.success 2, "Z Axis homing complete."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(_command, {:error, :report_axis_timeout_x}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Logger.error 2, "X Axis timeout."
|
||||
Farmbot.Logger.error 2, "X Axis timeout."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(_command, {:error, :report_axis_timeout_y}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Logger.error 2, "Y Axis timeout."
|
||||
Farmbot.Logger.error 2, "Y Axis timeout."
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_log_complete(_command, {:error, :report_axis_timeout_z}) do
|
||||
unless get_config_value(:bool, "settings", "firmware_input_log") do
|
||||
Logger.error 2, "Z Axis timeout."
|
||||
Farmbot.Logger.error 2, "Z Axis timeout."
|
||||
end
|
||||
end
|
||||
|
|
@ -3,7 +3,7 @@ defmodule Farmbot.Firmware.EstopTimer do
|
|||
Module responsible for timing emails about E stops.
|
||||
"""
|
||||
use GenServer
|
||||
use Farmbot.Logger
|
||||
require Farmbot.Logger
|
||||
|
||||
@msg "Farmbot has been E-Stopped for more than 10 minutes."
|
||||
# Ten minutes.
|
||||
|
@ -27,8 +27,8 @@ defmodule Farmbot.Firmware.EstopTimer do
|
|||
end
|
||||
|
||||
@doc false
|
||||
def start_link do
|
||||
GenServer.start_link(__MODULE__, [], [name: __MODULE__])
|
||||
def start_link(args) do
|
||||
GenServer.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
@ -59,7 +59,7 @@ defmodule Farmbot.Firmware.EstopTimer do
|
|||
if state.already_sent do
|
||||
{:noreply, %{state | timer: nil}}
|
||||
else
|
||||
Logger.warn 1, @msg, [channels: [:fatal_email]]
|
||||
Farmbot.Logger.warn 1, @msg, [channels: [:fatal_email]]
|
||||
{:noreply, %{state | timer: nil, already_sent: true}}
|
||||
end
|
||||
end
|
|
@ -2,12 +2,11 @@ defmodule Farmbot.Firmware do
|
|||
@moduledoc "Allows communication with the firmware."
|
||||
|
||||
use GenStage
|
||||
use Farmbot.Logger
|
||||
alias Farmbot.Bootstrap.SettingsSync
|
||||
require Farmbot.Logger
|
||||
alias Farmbot.Firmware.{Command, CompletionLogs, Vec3, EstopTimer, Utils}
|
||||
import Utils
|
||||
|
||||
import Farmbot.System.ConfigStorage,
|
||||
import Farmbot.Config,
|
||||
only: [get_config_value: 3, update_config_value: 4, get_config_as_map: 0]
|
||||
|
||||
import CompletionLogs,
|
||||
|
@ -109,8 +108,8 @@ defmodule Farmbot.Firmware do
|
|||
end
|
||||
|
||||
@doc "Start the firmware services."
|
||||
def start_link do
|
||||
GenStage.start_link(__MODULE__, [], name: __MODULE__)
|
||||
def start_link(args) do
|
||||
GenStage.start_link(__MODULE__, args, name: __MODULE__)
|
||||
end
|
||||
|
||||
## GenStage
|
||||
|
@ -155,7 +154,7 @@ defmodule Farmbot.Firmware do
|
|||
|
||||
def init([]) do
|
||||
handler_mod =
|
||||
Application.get_env(:farmbot, :behaviour)[:firmware_handler] || raise("No fw handler.")
|
||||
Application.get_env(:farmbot_core, :behaviour)[:firmware_handler] || raise("No fw handler.")
|
||||
|
||||
case handler_mod.start_link() do
|
||||
{:ok, handler} ->
|
||||
|
@ -168,7 +167,7 @@ defmodule Farmbot.Firmware do
|
|||
}
|
||||
{:error, reason} ->
|
||||
replace_firmware_handler(Farmbot.Firmware.StubHandler)
|
||||
Logger.error 1, "Failed to initialize firmware: #{inspect reason} Falling back to stub implementation."
|
||||
Farmbot.Logger.error 1, "Failed to initialize firmware: #{inspect reason} Falling back to stub implementation."
|
||||
init([])
|
||||
end
|
||||
|
||||
|
@ -192,7 +191,7 @@ defmodule Farmbot.Firmware do
|
|||
end
|
||||
|
||||
def handle_info({:EXIT, _, reason}, state) do
|
||||
Logger.error 1, "Firmware handler: #{state.handler_mod} died: #{inspect reason}"
|
||||
Farmbot.Logger.error 1, "Firmware handler: #{state.handler_mod} died: #{inspect reason}"
|
||||
case state.handler_mod.start_link() do
|
||||
{:ok, handler} ->
|
||||
new_state = %{state | handler: handler}
|
||||
|
@ -208,7 +207,7 @@ defmodule Farmbot.Firmware do
|
|||
case state.current do
|
||||
# Check if this timeout is actually talking about the current command.
|
||||
^timeout_command = current ->
|
||||
Logger.warn 1, "Timed out waiting for Firmware response. Retrying #{inspect current}) "
|
||||
Farmbot.Logger.warn 1, "Timed out waiting for Firmware response. Retrying #{inspect current}) "
|
||||
case apply(state.handler_mod, current.fun, [state.handler | current.args]) do
|
||||
:ok ->
|
||||
timer = start_timer(current, state.timeout_ms)
|
||||
|
@ -220,7 +219,7 @@ defmodule Farmbot.Firmware do
|
|||
|
||||
# If this timeout was not talking about the current command
|
||||
%Command{} = current ->
|
||||
Logger.debug 3, "Got stray timeout for command: #{inspect current}"
|
||||
Farmbot.Logger.debug 3, "Got stray timeout for command: #{inspect current}"
|
||||
{:noreply, [], %{state | timer: nil}}
|
||||
|
||||
# If there is no current command, we got a different kind of stray.
|
||||
|
@ -272,7 +271,7 @@ defmodule Farmbot.Firmware do
|
|||
end
|
||||
|
||||
defp do_queue_cmd(%Command{fun: _fun, args: _args, from: _from} = current, state) do
|
||||
# Logger.busy 3, "FW Queuing: #{fun}: #{inspect from}"
|
||||
# Farmbot.Logger.busy 3, "FW Queuing: #{fun}: #{inspect from}"
|
||||
new_q = :queue.in(current, state.queue)
|
||||
{:noreply, [], %{state | queue: new_q}}
|
||||
end
|
||||
|
@ -306,7 +305,7 @@ defmodule Farmbot.Firmware do
|
|||
|
||||
defp handle_gcode({:debug_message, message}, state) do
|
||||
if get_config_value(:bool, "settings", "arduino_debug_messages") do
|
||||
Logger.debug 3, "Arduino debug message: #{message}"
|
||||
Farmbot.Logger.debug 3, "Arduino debug message: #{message}"
|
||||
end
|
||||
{nil, state}
|
||||
end
|
||||
|
@ -314,7 +313,7 @@ defmodule Farmbot.Firmware do
|
|||
defp handle_gcode(code, state) when code in [:error, :invalid_command] do
|
||||
maybe_cancel_timer(state.timer, state.current)
|
||||
if state.current do
|
||||
Logger.error 1, "Got #{code} while executing `#{inspect state.current}`."
|
||||
Farmbot.Logger.error 1, "Got #{code} while executing `#{inspect state.current}`."
|
||||
do_reply(state, {:error, :firmware_error})
|
||||
{nil, %{state | current: nil}}
|
||||
else
|
||||
|
@ -323,47 +322,46 @@ defmodule Farmbot.Firmware do
|
|||
end
|
||||
|
||||
defp handle_gcode(:report_no_config, state) do
|
||||
Logger.busy 1, "Initializing Firmware."
|
||||
Farmbot.Logger.busy 1, "Initializing Firmware."
|
||||
old = get_config_as_map()["hardware_params"]
|
||||
spawn __MODULE__, :do_read_params, [Map.delete(old, "param_version")]
|
||||
{nil, %{state | initialized: false, initializing: true}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:report_params_complete, state) do
|
||||
Logger.success 1, "Firmware Initialized."
|
||||
Farmbot.Logger.success 1, "Firmware Initialized."
|
||||
{nil, %{state | initialized: true, initializing: false}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:idle, %{initialized: false, initializing: false} = state) do
|
||||
Logger.busy 3, "Firmware not initialized yet. Waiting for R88 message."
|
||||
Farmbot.Logger.busy 3, "Firmware not initialized yet. Waiting for R88 message."
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
defp handle_gcode(:idle, %{initialized: true, initializing: false, current: nil, z_needs_home_on_boot: true} = state) do
|
||||
Logger.info 2, "Bootup homing Z axis"
|
||||
Farmbot.Logger.info 2, "Bootup homing Z axis"
|
||||
spawn __MODULE__, :find_home, [:z]
|
||||
{nil, %{state | z_needs_home_on_boot: false}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:idle, %{initialized: true, initializing: false, current: nil, y_needs_home_on_boot: true} = state) do
|
||||
Logger.info 2, "Bootup homing Y axis"
|
||||
Farmbot.Logger.info 2, "Bootup homing Y axis"
|
||||
spawn __MODULE__, :find_home, [:y]
|
||||
{nil, %{state | y_needs_home_on_boot: false}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:idle, %{initialized: true, initializing: false, current: nil, x_needs_home_on_boot: true} = state) do
|
||||
Logger.info 2, "Bootup homing X axis"
|
||||
Farmbot.Logger.info 2, "Bootup homing X axis"
|
||||
spawn __MODULE__, :find_home, [:x]
|
||||
{nil, %{state | x_needs_home_on_boot: false}}
|
||||
end
|
||||
|
||||
defp handle_gcode(:idle, state) do
|
||||
maybe_cancel_timer(state.timer, state.current)
|
||||
Farmbot.BotState.set_busy(false)
|
||||
if state.current do
|
||||
Logger.warn 1, "Got idle while executing a command."
|
||||
timer = start_timer(state.current, state.timeout_ms)
|
||||
{nil, %{state | timer: timer}}
|
||||
Farmbot.Logger.warn 1, "Got idle while executing a command."
|
||||
timer = start_timer(state.current, state.timeout_ms)
|
||||
{nil, %{state | timer: timer}}
|
||||
else
|
||||
{:informational_settings, %{busy: false, locked: false}, %{state | idle: true}}
|
||||
end
|
||||
|
@ -388,7 +386,7 @@ defmodule Farmbot.Firmware do
|
|||
end
|
||||
|
||||
defp handle_gcode({:report_pin_mode, pin, mode_atom}, state) do
|
||||
# Logger.debug 3, "Got pin mode report: #{pin}: #{mode_atom}"
|
||||
# Farmbot.Logger.debug 3, "Got pin mode report: #{pin}: #{mode_atom}"
|
||||
mode = extract_pin_mode(mode_atom)
|
||||
case state.pins[pin] do
|
||||
%{mode: _, value: _} = pin_map ->
|
||||
|
@ -399,7 +397,7 @@ defmodule Farmbot.Firmware do
|
|||
end
|
||||
|
||||
defp handle_gcode({:report_pin_value, pin, value}, state) do
|
||||
# Logger.debug 3, "Got pin value report: #{pin}: #{value} old: #{inspect state.pins[pin]}"
|
||||
# Farmbot.Logger.debug 3, "Got pin value report: #{pin}: #{value} old: #{inspect state.pins[pin]}"
|
||||
case state.pins[pin] do
|
||||
%{mode: _, value: _} = pin_map ->
|
||||
{:pins, %{pin => %{pin_map | value: value}}, %{state | pins: %{state.pins | pin => %{pin_map | value: value}}}}
|
||||
|
@ -409,18 +407,18 @@ defmodule Farmbot.Firmware do
|
|||
end
|
||||
|
||||
defp handle_gcode({:report_parameter_value, param, value}, state) when (value == -1) do
|
||||
value = maybe_update_param_from_report(to_string(param), nil)
|
||||
maybe_update_param_from_report(to_string(param), nil)
|
||||
{:mcu_params, %{param => nil}, %{state | params: Map.put(state.params, param, value)}}
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_parameter_value, param, value}, state) when is_number(value) do
|
||||
value = maybe_update_param_from_report(to_string(param), value)
|
||||
maybe_update_param_from_report(to_string(param), value)
|
||||
{:mcu_params, %{param => value}, %{state | params: Map.put(state.params, param, value)}}
|
||||
end
|
||||
|
||||
defp handle_gcode({:report_software_version, version}, state) do
|
||||
hw = get_config_value(:string, "settings", "firmware_hardware")
|
||||
Logger.debug 3, "Firmware reported software version: #{version} current firmware_hardware is: #{hw}"
|
||||
Farmbot.Logger.debug 3, "Firmware reported software version: #{version} current firmware_hardware is: #{hw}"
|
||||
case String.last(version) do
|
||||
"F" ->
|
||||
update_config_value(:string, "settings", "firmware_hardware", "farmduino")
|
||||
|
@ -476,7 +474,6 @@ defmodule Farmbot.Firmware do
|
|||
end
|
||||
|
||||
defp handle_gcode(:busy, state) do
|
||||
Farmbot.BotState.set_busy(true)
|
||||
maybe_cancel_timer(state.timer, state.current)
|
||||
timer = if state.current do
|
||||
start_timer(state.current, state.timeout_ms)
|
||||
|
@ -488,12 +485,11 @@ defmodule Farmbot.Firmware do
|
|||
|
||||
defp handle_gcode(:done, state) do
|
||||
maybe_cancel_timer(state.timer, state.current)
|
||||
Farmbot.BotState.set_busy(false)
|
||||
if state.current do
|
||||
do_reply(state, :ok)
|
||||
{nil, %{state | current: nil}}
|
||||
{:informational_settings, %{busy: true}, %{state | current: nil}}
|
||||
else
|
||||
{nil, state}
|
||||
{:informational_settings, %{busy: true}, state}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -505,7 +501,7 @@ defmodule Farmbot.Firmware do
|
|||
|
||||
defp handle_gcode({:report_calibration, axis, status}, state) do
|
||||
maybe_cancel_timer(state.timer, state.current)
|
||||
Logger.busy 1, "Axis #{axis} calibration: #{status}"
|
||||
Farmbot.Logger.busy 1, "Axis #{axis} calibration: #{status}"
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
|
@ -527,7 +523,7 @@ defmodule Farmbot.Firmware do
|
|||
end
|
||||
|
||||
defp handle_gcode(code, state) do
|
||||
Logger.warn(3, "unhandled code: #{inspect(code)}")
|
||||
Farmbot.Logger.warn(3, "unhandled code: #{inspect(code)}")
|
||||
{nil, state}
|
||||
end
|
||||
|
||||
|
@ -540,11 +536,14 @@ defmodule Farmbot.Firmware do
|
|||
:ok
|
||||
end
|
||||
|
||||
defp start_timer(%Command{} = command, timeout) do
|
||||
Process.send_after(self(), {:command_timeout, command}, timeout)
|
||||
end
|
||||
|
||||
defp maybe_update_param_from_report(param, val) when is_binary(param) do
|
||||
real_val = if val, do: (val / 1), else: nil
|
||||
# Logger.debug 3, "Firmware reported #{param} => #{real_val || "nil"}"
|
||||
# Farmbot.Logger.debug 3, "Firmware reported #{param} => #{val || -1}"
|
||||
update_config_value(:float, "hardware_params", to_string(param), real_val)
|
||||
real_val
|
||||
end
|
||||
|
||||
@doc false
|
||||
|
@ -554,7 +553,8 @@ defmodule Farmbot.Firmware do
|
|||
(float_val == -1) -> :ok
|
||||
is_nil(float_val) -> :ok
|
||||
is_number(float_val) ->
|
||||
:ok = update_param(:"#{key}", float_val / 1)
|
||||
val = round(float_val)
|
||||
:ok = update_param(:"#{key}", val)
|
||||
end
|
||||
end
|
||||
:ok = update_param(:param_use_eeprom, 0)
|
||||
|
@ -576,13 +576,14 @@ defmodule Farmbot.Firmware do
|
|||
str_param = to_string(param)
|
||||
case get_config_value(:float, "hardware_params", str_param) do
|
||||
^val ->
|
||||
Logger.success 1, "Calibrated #{param}: #{val}"
|
||||
SettingsSync.upload_fw_kv(str_param, val)
|
||||
Farmbot.Logger.success 1, "Calibrated #{param}: #{val}"
|
||||
# SettingsSync.upload_fw_kv(str_param, val)
|
||||
raise("fixme")
|
||||
:ok
|
||||
_ -> report_calibration_callback(tries - 1, param, val)
|
||||
end
|
||||
{:error, reason} ->
|
||||
Logger.error 1, "Failed to set #{param}: #{val} (#{inspect reason})"
|
||||
Farmbot.Logger.error 1, "Failed to set #{param}: #{val} (#{inspect reason})"
|
||||
report_calibration_callback(tries - 1, param, val)
|
||||
end
|
||||
end
|
||||
|
@ -598,10 +599,10 @@ defmodule Farmbot.Firmware do
|
|||
%Command{fun: :emergency_lock, from: from} ->
|
||||
:ok = GenServer.reply from, {:error, :emergency_lock}
|
||||
%Command{fun: _fun, from: from} ->
|
||||
# Logger.success 3, "FW Replying: #{fun}: #{inspect from}"
|
||||
# Farmbot.Logger.success 3, "FW Replying: #{fun}: #{inspect from}"
|
||||
:ok = GenServer.reply from, reply
|
||||
nil ->
|
||||
Logger.error 1, "FW Nothing to send reply: #{inspect reply} to!."
|
||||
Farmbot.Logger.error 1, "FW Nothing to send reply: #{inspect reply} to!."
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
@ -613,8 +614,4 @@ defmodule Farmbot.Firmware do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp start_timer(%Command{} = command, timeout) do
|
||||
Process.send_after(self(), {:command_timeout, command}, timeout)
|
||||
end
|
||||
end
|
|
@ -75,7 +75,7 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
end
|
||||
|
||||
@spec parse_report_calibration(binary)
|
||||
:: {binary, {:report_calibration, binary, binary}}
|
||||
:: {binary, {:report_calibration, binary, :idle | :home | :end}}
|
||||
defp parse_report_calibration(r) do
|
||||
[axis_and_status | [q]] = String.split(r, " Q")
|
||||
<<a::size(8), b::size(8)>> = axis_and_status
|
||||
|
@ -118,7 +118,7 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
| :report_encoder_position_raw
|
||||
|
||||
@spec report_xyz(binary, reporter)
|
||||
:: {binary, {reporter, binary, binary, binary}}
|
||||
:: {binary, {reporter, float(), float(), float()}}
|
||||
defp report_xyz(position, reporter) when is_bitstring(position),
|
||||
do: position |> String.split(" ") |> do_parse_pos(reporter)
|
||||
|
||||
|
@ -146,8 +146,7 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
|
||||
@doc false
|
||||
@spec parse_end_stops(binary)
|
||||
:: {:report_end_stops,
|
||||
binary, binary, binary, binary, binary, binary, binary}
|
||||
:: {binary(), {:report_end_stops, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1}}
|
||||
def parse_end_stops(
|
||||
<<"XA", xa::size(8), 32,
|
||||
"XB", xb::size(8), 32,
|
||||
|
@ -167,9 +166,6 @@ defmodule Farmbot.Firmware.Gcode.Parser do
|
|||
defp pes(48), do: 0
|
||||
defp pes(49), do: 1
|
||||
|
||||
@doc false
|
||||
@spec parse_pvq(binary, :report_parameter_value) ::
|
||||
{:report_parameter_value, atom, integer, String.t()}
|
||||
def parse_pvq(params, :report_parameter_value)
|
||||
when is_bitstring(params),
|
||||
do: params |> String.split(" ") |> do_parse_params
|
|
@ -2,7 +2,7 @@ defmodule Farmbot.Firmware.StubHandler do
|
|||
@moduledoc "Stubs out firmware functionality when you don't have an arduino."
|
||||
|
||||
use GenStage
|
||||
use Farmbot.Logger
|
||||
require Farmbot.Logger
|
||||
|
||||
@behaviour Farmbot.Firmware.Handler
|
||||
alias Farmbot.Firmware.Vec3
|
||||
|
@ -10,7 +10,7 @@ defmodule Farmbot.Firmware.StubHandler do
|
|||
## Firmware Handler Behaviour.
|
||||
|
||||
def start_link do
|
||||
Logger.warn(3, "Firmware is being stubbed.")
|
||||
Farmbot.Logger.warn(3, "Firmware is being stubbed.")
|
||||
GenStage.start_link(__MODULE__, [])
|
||||
end
|
||||
|
||||
|
@ -110,7 +110,12 @@ defmodule Farmbot.Firmware.StubHandler do
|
|||
dispatch = if state.locked? do
|
||||
[:report_emergency_lock]
|
||||
else
|
||||
[:idle]
|
||||
[
|
||||
{:report_current_position, state.pos.x, state.pos.y, state.pos.z},
|
||||
{:report_encoder_position_scaled, state.pos.x, state.pos.y, state.pos.z},
|
||||
{:report_encoder_position_raw, state.pos.x, state.pos.y, state.pos.z},
|
||||
:idle,
|
||||
]
|
||||
end
|
||||
{:noreply, dispatch, state}
|
||||
end
|
||||
|
@ -120,7 +125,7 @@ defmodule Farmbot.Firmware.StubHandler do
|
|||
{:noreply, [:idle, :report_no_config, :idle], state}
|
||||
end
|
||||
|
||||
def handle_call(cmd, _from, %{locked?: true} = state) when cmd != :emergency_unlock do
|
||||
def handle_call(cmd, _from, %{locked?: true} = state) when cmd not in [:emergency_unlock, :update_param] do
|
||||
{:reply, :ok, [:error], state}
|
||||
end
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
defmodule Farmbot.Firmware.Supervisor do
|
||||
@moduledoc false
|
||||
use Supervisor
|
||||
|
||||
@doc "Reinitializes the Firmware stack. Warning has MANY SIDE EFFECTS."
|
||||
def reinitialize do
|
||||
Farmbot.Firmware.UartHandler.AutoDetector.start_link([])
|
||||
Supervisor.terminate_child(Farmbot.Bootstrap.Supervisor, Farmbot.Firmware.Supervisor)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def start_link(args) do
|
||||
Supervisor.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
children = [
|
||||
{Farmbot.Firmware.EstopTimer, []},
|
||||
{Farmbot.Firmware, []},
|
||||
]
|
||||
|
||||
Supervisor.init(children, [strategy: :one_for_one])
|
||||
end
|
||||
end
|
|
@ -14,7 +14,8 @@ defmodule Farmbot.Firmware.UartHandler.AutoDetector do
|
|||
alias Circuits.UART
|
||||
alias Farmbot.Firmware.{UartHandler, StubHandler, Utils}
|
||||
import Utils
|
||||
use Farmbot.Logger
|
||||
require Farmbot.Logger
|
||||
use GenServer
|
||||
|
||||
#TODO(Connor) - Maybe make this configurable?
|
||||
case Farmbot.Project.target() do
|
||||
|
@ -34,8 +35,8 @@ defmodule Farmbot.Firmware.UartHandler.AutoDetector do
|
|||
end
|
||||
|
||||
@doc false
|
||||
def start_link(_, _) do
|
||||
GenServer.start_link(__MODULE__, [])
|
||||
def start_link(args) do
|
||||
GenServer.start_link(__MODULE__, args, [name: __MODULE__])
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
|
@ -47,12 +48,12 @@ defmodule Farmbot.Firmware.UartHandler.AutoDetector do
|
|||
case auto_detect() do
|
||||
[dev] ->
|
||||
dev = "/dev/#{dev}"
|
||||
Logger.success 3, "detected target UART: #{dev}"
|
||||
Farmbot.Logger.success 3, "detected target UART: #{dev}"
|
||||
replace_firmware_handler(UartHandler)
|
||||
Application.put_env(:farmbot, :uart_handler, tty: dev)
|
||||
Application.put_env(:farmbot_core, :uart_handler, tty: dev)
|
||||
dev
|
||||
_ ->
|
||||
Logger.error 1, "Could not detect a UART device."
|
||||
Farmbot.Logger.error 1, "Could not detect a UART device."
|
||||
replace_firmware_handler(StubHandler)
|
||||
:error
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Farmbot.Firmware.UartHandler.Framing do
|
||||
@behaviour Circuits.UART.Framing
|
||||
import Farmbot.Firmware.Gcode.Parser
|
||||
use Farmbot.Logger
|
||||
require Farmbot.Logger
|
||||
|
||||
# credo:disable-for-this-file Credo.Check.Refactor.FunctionArity
|
||||
|
||||
|
@ -42,14 +42,14 @@ defmodule Farmbot.Firmware.UartHandler.Framing do
|
|||
separator = Keyword.get(args, :separator, "\n")
|
||||
|
||||
log_input =
|
||||
Farmbot.System.ConfigStorage.get_config_value(
|
||||
Farmbot.Config.get_config_value(
|
||||
:bool,
|
||||
"settings",
|
||||
"firmware_input_log"
|
||||
)
|
||||
|
||||
log_output =
|
||||
Farmbot.System.ConfigStorage.get_config_value(
|
||||
Farmbot.Config.get_config_value(
|
||||
:bool,
|
||||
"settings",
|
||||
"firmware_output_log"
|
||||
|
@ -68,7 +68,7 @@ defmodule Farmbot.Firmware.UartHandler.Framing do
|
|||
def add_framing(data, state) do
|
||||
# maybe log output here
|
||||
if state.log_output do
|
||||
Logger.debug(3, data)
|
||||
Farmbot.Logger.debug(3, data)
|
||||
end
|
||||
|
||||
{:ok, data <> state.separator, state}
|
||||
|
@ -179,13 +179,13 @@ defmodule Farmbot.Firmware.UartHandler.Framing do
|
|||
|
||||
defp do_parse_code(processed, log_input) do
|
||||
if log_input do
|
||||
Logger.debug(3, processed)
|
||||
Farmbot.Logger.debug(3, processed)
|
||||
end
|
||||
|
||||
parse_code(processed)
|
||||
rescue
|
||||
er ->
|
||||
Logger.error(1, "Firmware parser error: #{Exception.message(er)}")
|
||||
Farmbot.Logger.error(1, "Firmware parser error: #{Exception.message(er)}")
|
||||
{nil, :noop}
|
||||
end
|
||||
end
|
|
@ -4,12 +4,11 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
"""
|
||||
|
||||
use GenStage
|
||||
alias Circuits.UART
|
||||
use Farmbot.Logger
|
||||
alias Farmbot.System.ConfigStorage
|
||||
import ConfigStorage, only: [update_config_value: 4, get_config_value: 3]
|
||||
alias Nerves.UART
|
||||
require Farmbot.Logger
|
||||
import Farmbot.Config, only: [update_config_value: 4, get_config_value: 3]
|
||||
alias Farmbot.Firmware
|
||||
alias Firmware.{UartHandler, Utils}
|
||||
alias Firmware.Utils
|
||||
import Utils
|
||||
|
||||
@behaviour Firmware.Handler
|
||||
|
@ -96,13 +95,13 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
end
|
||||
|
||||
def init([]) do
|
||||
Logger.debug 3, "Uart handler init."
|
||||
Farmbot.Logger.debug 3, "Uart handler init."
|
||||
# If in dev environment,
|
||||
# it is expected that this be done at compile time.
|
||||
# If in target environment,
|
||||
# this should be done by `Farmbot.Firmware.AutoDetector`.
|
||||
error_msg = "Please configure uart handler!"
|
||||
tty = Application.get_env(:farmbot, :uart_handler)[:tty] || raise error_msg
|
||||
tty = Application.get_env(:farmbot_core, :uart_handler)[:tty] || raise error_msg
|
||||
|
||||
# Disable fw input logs after a reset of the
|
||||
# Fw handler if they were enabled.
|
||||
|
@ -143,28 +142,8 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
%{state | config_busy: true}
|
||||
end
|
||||
|
||||
defp handle_config({:config, "settings", "firmware_hardware", val}, state) do
|
||||
if val != state.hw do
|
||||
if Process.whereis(Farmbot.BotState) do
|
||||
Logger.info 3, "firmware_hardware updated from #{state.hw} to #{val}"
|
||||
Farmbot.BotState.set_sync_status(:maintenance)
|
||||
|
||||
UART.close(state.nerves)
|
||||
UartHandler.Update.force_update_firmware(val)
|
||||
open_tty(state.tty, state.nerves)
|
||||
|
||||
Farmbot.BotState.set_sync_status(:sync_now)
|
||||
%{state | hw: val, config_busy: true}
|
||||
else # if BotState not alive
|
||||
# This happens when you select a fw, then go back and select a different one
|
||||
# in configurator.
|
||||
Logger.warn 3, "got invalid firmware hardware update: current: #{state.hw} update: #{val}"
|
||||
update_config_value(:string, "settings", "firmware_hardware", state.hw)
|
||||
state
|
||||
end
|
||||
else # if thae value didn't change
|
||||
state
|
||||
end
|
||||
defp handle_config({:config, "settings", "firmware_hardware", _val}, _state) do
|
||||
raise("FIXME")
|
||||
end
|
||||
|
||||
defp handle_config(_, state) do
|
||||
|
@ -172,7 +151,7 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
end
|
||||
|
||||
defp open_tty(tty, nerves \\ nil) do
|
||||
Logger.debug 3, "Opening uart device: #{tty}"
|
||||
Farmbot.Logger.debug 3, "Opening uart device: #{tty}"
|
||||
nerves = nerves || UART.start_link |> elem(1)
|
||||
Process.link(nerves)
|
||||
case UART.open(nerves, tty, [speed: 115_200, active: true]) do
|
||||
|
@ -189,7 +168,7 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
defp loop_until_idle(nerves, idle_count \\ 0)
|
||||
|
||||
defp loop_until_idle(nerves, 2) do
|
||||
Logger.success 3, "Got two idles. UART is up."
|
||||
Farmbot.Logger.success 3, "Got two idles. UART is up."
|
||||
Process.sleep(1500)
|
||||
{:ok, nerves}
|
||||
end
|
||||
|
@ -197,18 +176,18 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
defp loop_until_idle(nerves, idle_count)
|
||||
when is_pid(nerves) and is_number(idle_count)
|
||||
do
|
||||
Logger.debug 3, "Waiting for firmware idle."
|
||||
Farmbot.Logger.debug 3, "Waiting for firmware idle."
|
||||
receive do
|
||||
{:circuits_uart, _, {:error, reason}} -> {:stop, reason}
|
||||
{:circuits_uart, _, {:partial, _}} -> loop_until_idle(nerves, idle_count)
|
||||
{:circuits_uart, _, {_, :idle}} -> loop_until_idle(nerves, idle_count + 1)
|
||||
{:circuits_uart, _, {_, {:debug_message, msg}}} ->
|
||||
if String.contains?(msg, "STARTUP") do
|
||||
Logger.success 3, "Got #{msg}. UART is up."
|
||||
Farmbot.Logger.success 3, "Got #{msg}. UART is up."
|
||||
UART.write(nerves, "F22 P2 V0")
|
||||
{:ok, nerves}
|
||||
else
|
||||
Logger.debug 3, "Got arduino debug while booting up: #{msg}"
|
||||
Farmbot.Logger.debug 3, "Got arduino debug while booting up: #{msg}"
|
||||
loop_until_idle(nerves, idle_count)
|
||||
end
|
||||
{:circuits_uart, _, _msg} -> loop_until_idle(nerves, idle_count)
|
||||
|
@ -227,7 +206,7 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
end
|
||||
|
||||
def terminate(reason, state) do
|
||||
Logger.warn 1, "UART handler died: #{inspect reason}"
|
||||
Farmbot.Logger.warn 1, "UART handler died: #{inspect reason}"
|
||||
if state.nerves do
|
||||
UART.close(state.nerves)
|
||||
UART.stop(state.nerves)
|
||||
|
@ -237,10 +216,10 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
# if there is an error, we assume something bad has happened, and we probably
|
||||
# Are better off crashing here, and being restarted.
|
||||
def handle_info({:circuits_uart, _, {:error, :eio}}, state) do
|
||||
Logger.error 1, "UART device removed."
|
||||
old_env = Application.get_env(:farmbot, :behaviour)
|
||||
Farmbot.Logger.error 1, "UART device removed."
|
||||
old_env = Application.get_env(:farmbot_core, :behaviour)
|
||||
new_env = Keyword.put(old_env, :firmware_handler, Firmware.StubHandler)
|
||||
Application.put_env(:farmbot, :behaviour, new_env)
|
||||
Application.put_env(:farmbot_core, :behaviour, new_env)
|
||||
{:stop, {:error, :eio}, state}
|
||||
end
|
||||
|
||||
|
@ -250,20 +229,20 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
|
||||
# Unhandled gcodes just get ignored.
|
||||
def handle_info({:circuits_uart, _, {:unhandled_gcode, code_str}}, state) do
|
||||
Logger.debug 3, "Got unhandled gcode: #{code_str}"
|
||||
Farmbot.Logger.debug 3, "Got unhandled gcode: #{code_str}"
|
||||
{:noreply, [], state}
|
||||
end
|
||||
|
||||
def handle_info({:circuits_uart, _, {_, {:report_software_version, v}}}, state) do
|
||||
expected = Application.get_env(:farmbot, :expected_fw_versions)
|
||||
expected = Application.get_env(:farmbot_core, :expected_fw_versions)
|
||||
if v in expected do
|
||||
{:noreply, [{:report_software_version, v}], state}
|
||||
else
|
||||
err = "Firmware version #{v} is not in expected versions: #{inspect expected}"
|
||||
Logger.error 1, err
|
||||
old_env = Application.get_env(:farmbot, :behaviour)
|
||||
Farmbot.Logger.error 1, err
|
||||
old_env = Application.get_env(:farmbot_core, :behaviour)
|
||||
new_env = Keyword.put(old_env, :firmware_handler, Firmware.StubHandler)
|
||||
Application.put_env(:farmbot, :behaviour, new_env)
|
||||
Application.put_env(:farmbot_core, :behaviour, new_env)
|
||||
{:stop, :normal, state}
|
||||
end
|
||||
end
|
||||
|
@ -281,7 +260,7 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
:ok
|
||||
else
|
||||
err = "Echo #{code} does not match #{state.current_cmd} (#{distance})"
|
||||
Logger.error 3, err
|
||||
Farmbot.Logger.error 3, err
|
||||
end
|
||||
{:noreply, [], %{state | current_cmd: nil}}
|
||||
end
|
||||
|
@ -295,12 +274,12 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
end
|
||||
|
||||
def handle_info({:circuits_uart, _, bin}, state) when is_binary(bin) do
|
||||
Logger.warn(3, "Unparsed Gcode: #{bin}")
|
||||
Farmbot.Logger.warn(3, "Unparsed Gcode: #{bin}")
|
||||
{:noreply, [], state}
|
||||
end
|
||||
|
||||
defp do_write(bin, state, dispatch \\ []) do
|
||||
# Logger.debug 3, "writing: #{bin}"
|
||||
# Farmbot.Logger.debug 3, "writing: #{bin}"
|
||||
case UART.write(state.nerves, bin) do
|
||||
:ok -> {:reply, :ok, dispatch, %{state | current_cmd: bin}}
|
||||
err -> {:reply, err, [], %{state | current_cmd: nil}}
|
||||
|
@ -392,7 +371,7 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
do_write("F42 P#{pin} M#{encoded_mode}", state, dispatch)
|
||||
end
|
||||
|
||||
def handle_call({:write_pin, pin, mode, value}, _from, state) when is_integer(value) do
|
||||
def handle_call({:write_pin, pin, mode, value}, _from, state) do
|
||||
encoded_mode = extract_pin_mode(mode)
|
||||
dispatch = [{:report_pin_mode, pin, mode}, {:report_pin_value, pin, value}]
|
||||
do_write("F41 P#{pin} V#{value} M#{encoded_mode}", state, dispatch)
|
||||
|
@ -402,7 +381,7 @@ defmodule Farmbot.Firmware.UartHandler do
|
|||
do_write("F83", state)
|
||||
end
|
||||
|
||||
def handle_call({:set_servo_angle, pin, angle}, _, state) when is_integer(angle) do
|
||||
def handle_call({:set_servo_angle, pin, angle}, _, state) do
|
||||
do_write("F61 P#{pin} V#{angle}", state)
|
||||
end
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
defmodule Farmbot.Firmware.UartHandler.Update do
|
||||
@moduledoc false
|
||||
|
||||
use Farmbot.Logger
|
||||
require Farmbot.Logger
|
||||
|
||||
@uart_speed 115_200
|
||||
alias Circuits.UART
|
||||
|
||||
def maybe_update_firmware(hardware \\ nil) do
|
||||
tty = Application.get_all_env(:farmbot)[:uart_handler][:tty]
|
||||
tty = Application.get_all_env(:farmbot_core)[:uart_handler][:tty]
|
||||
hardware = case hardware do
|
||||
"farmduino" -> "F"
|
||||
"arduino" -> "R"
|
||||
|
@ -20,7 +20,7 @@ defmodule Farmbot.Firmware.UartHandler.Update do
|
|||
end
|
||||
|
||||
def force_update_firmware(hardware \\ nil) do
|
||||
tty = Application.get_all_env(:farmbot)[:uart_handler][:tty]
|
||||
tty = Application.get_all_env(:farmbot_core)[:uart_handler][:tty]
|
||||
hardware = case hardware do
|
||||
"farmduino" -> "F"
|
||||
"arduino" -> "R"
|
||||
|
@ -40,29 +40,29 @@ defmodule Farmbot.Firmware.UartHandler.Update do
|
|||
]
|
||||
:ok = UART.open(uart, tty, [speed: @uart_speed])
|
||||
:ok = UART.configure(uart, opts)
|
||||
Logger.busy 3, "Waiting for firmware idle report."
|
||||
Farmbot.Logger.busy 3, "Waiting for firmware idle report."
|
||||
do_fw_loop(uart, tty, :idle, hardware)
|
||||
close(uart)
|
||||
{:error, reason} ->
|
||||
Logger.error 1, "Failed to connect to firmware for update: #{inspect reason}"
|
||||
Farmbot.Logger.error 1, "Failed to connect to firmware for update: #{inspect reason}"
|
||||
end
|
||||
end
|
||||
|
||||
defp do_fw_loop(uart, tty, flag, hardware) do
|
||||
receive do
|
||||
{:circuits_uart, _, {:error, reason}} ->
|
||||
Logger.error 1, "Failed to connect to firmware for update during idle step: #{inspect reason}"
|
||||
Farmbot.Logger.error 1, "Failed to connect to firmware for update during idle step: #{inspect reason}"
|
||||
{:circuits_uart, _, data} ->
|
||||
if String.contains?(data, "R00") do
|
||||
case flag do
|
||||
:idle ->
|
||||
Logger.busy 3, "Waiting for next idle."
|
||||
Farmbot.Logger.busy 3, "Waiting for next idle."
|
||||
do_fw_loop(uart, tty, :version, hardware)
|
||||
:version ->
|
||||
Process.sleep(500)
|
||||
# tell the FW to report its version.
|
||||
UART.write(uart, "F83")
|
||||
Logger.busy 3, "Waiting for firmware version report."
|
||||
Nerves.UART.write(uart, "F83")
|
||||
Farmbot.Logger.busy 3, "Waiting for firmware version report."
|
||||
do_wait_version(uart, tty, hardware)
|
||||
end
|
||||
else
|
||||
|
@ -70,7 +70,7 @@ defmodule Farmbot.Firmware.UartHandler.Update do
|
|||
end
|
||||
after
|
||||
15_000 ->
|
||||
Logger.warn 1, "timeout waiting for firmware idle. Forcing flash."
|
||||
Farmbot.Logger.warn 1, "timeout waiting for firmware idle. Forcing flash."
|
||||
do_flash(hardware, uart, tty)
|
||||
end
|
||||
end
|
||||
|
@ -78,7 +78,7 @@ defmodule Farmbot.Firmware.UartHandler.Update do
|
|||
defp do_wait_version(uart, tty, hardware) do
|
||||
receive do
|
||||
{:circuits_uart, _, {:error, reason}} ->
|
||||
Logger.error 1, "Failed to connect to firmware for update: #{inspect reason}"
|
||||
Farmbot.Logger.error 1, "Failed to connect to firmware for update: #{inspect reason}"
|
||||
{:circuits_uart, _, data} ->
|
||||
case String.split(data, "R83 ") do
|
||||
[_] ->
|
||||
|
@ -88,7 +88,7 @@ defmodule Farmbot.Firmware.UartHandler.Update do
|
|||
end
|
||||
after
|
||||
15_000 ->
|
||||
Logger.warn 1, "timeout waiting for firmware version. Forcing flash."
|
||||
Farmbot.Logger.warn 1, "timeout waiting for firmware version. Forcing flash."
|
||||
do_flash(hardware, uart, tty)
|
||||
end
|
||||
end
|
||||
|
@ -98,50 +98,50 @@ defmodule Farmbot.Firmware.UartHandler.Update do
|
|||
[ver] -> ver
|
||||
[ver, _] -> ver
|
||||
end
|
||||
expected = Application.get_env(:farmbot, :expected_fw_versions)
|
||||
expected = Application.get_env(:farmbot_core, :expected_fw_versions)
|
||||
fw_hw = String.last(current_version)
|
||||
cond do
|
||||
fw_hw != hardware ->
|
||||
Logger.warn 3, "Switching firmware hardware."
|
||||
Farmbot.Logger.warn 3, "Switching firmware hardware."
|
||||
do_flash(hardware, uart, tty)
|
||||
current_version in expected ->
|
||||
Logger.success 1, "Firmware is already correct version."
|
||||
Farmbot.Logger.success 1, "Firmware is already correct version."
|
||||
true ->
|
||||
Logger.busy 1, "#{current_version} != #{inspect expected}"
|
||||
Farmbot.Logger.busy 1, "#{current_version} != #{inspect expected}"
|
||||
do_flash(fw_hw, uart, tty)
|
||||
end
|
||||
end
|
||||
|
||||
# Farmduino
|
||||
defp do_flash("F", uart, tty) do
|
||||
avrdude("#{:code.priv_dir(:farmbot)}/eeprom_clear.ino.hex", uart, tty)
|
||||
avrdude("#{:code.priv_dir(:farmbot_core)}/eeprom_clear.ino.hex", uart, tty)
|
||||
Process.sleep(1000)
|
||||
avrdude("#{:code.priv_dir(:farmbot)}/farmduino.hex", uart, tty)
|
||||
avrdude("#{:code.priv_dir(:farmbot_core)}/farmduino.hex", uart, tty)
|
||||
end
|
||||
|
||||
defp do_flash("G", uart, tty) do
|
||||
avrdude("#{:code.priv_dir(:farmbot)}/eeprom_clear.ino.hex", uart, tty)
|
||||
avrdude("#{:code.priv_dir(:farmbot_core)}/eeprom_clear.ino.hex", uart, tty)
|
||||
Process.sleep(1000)
|
||||
avrdude("#{:code.priv_dir(:farmbot)}/farmduino_k14.hex", uart, tty)
|
||||
avrdude("#{:code.priv_dir(:farmbot_core)}/farmduino_k14.hex", uart, tty)
|
||||
end
|
||||
|
||||
# Anything else. (should always be "R")
|
||||
defp do_flash(_, uart, tty) do
|
||||
avrdude("#{:code.priv_dir(:farmbot)}/eeprom_clear.ino.hex", uart, tty)
|
||||
avrdude("#{:code.priv_dir(:farmbot_core)}/eeprom_clear.ino.hex", uart, tty)
|
||||
Process.sleep(1000)
|
||||
avrdude("#{:code.priv_dir(:farmbot)}/arduino_firmware.hex", uart, tty)
|
||||
avrdude("#{:code.priv_dir(:farmbot_core)}/arduino_firmware.hex", uart, tty)
|
||||
end
|
||||
|
||||
defp close(nil) do
|
||||
Logger.info 3, "No uart process."
|
||||
Farmbot.Logger.info 3, "No uart process."
|
||||
:ok
|
||||
end
|
||||
|
||||
defp close(uart) do
|
||||
if Process.alive?(uart) do
|
||||
close = UART.close(uart)
|
||||
stop = UART.stop(uart)
|
||||
Logger.info 3, "CLOSE: #{inspect close} STOP: #{stop}"
|
||||
close = Nerves.UART.close(uart)
|
||||
stop = Nerves.UART.stop(uart)
|
||||
Farmbot.Logger.info 3, "CLOSE: #{inspect close} STOP: #{stop}"
|
||||
Process.sleep(2000) # to allow the FD to be closed.
|
||||
end
|
||||
end
|
||||
|
@ -149,7 +149,7 @@ defmodule Farmbot.Firmware.UartHandler.Update do
|
|||
def avrdude(fw_file, uart, tty) do
|
||||
close(uart)
|
||||
args = ~w"-patmega2560 -cwiring -P#{tty} -b#{@uart_speed} -D -V -Uflash:w:#{fw_file}:i"
|
||||
Logger.busy 3, "Starting avrdude: #{inspect(args)}"
|
||||
Farmbot.Logger.busy 3, "Starting avrdude: #{inspect(args)}"
|
||||
reset = reset_init()
|
||||
opts = [stderr_to_stdout: true, into: IO.stream(:stdio, :line)]
|
||||
do_reset(reset)
|
||||
|
@ -158,10 +158,10 @@ defmodule Farmbot.Firmware.UartHandler.Update do
|
|||
Process.sleep(1500) # wait to allow file descriptors to be closed.
|
||||
case res do
|
||||
{_, 0} ->
|
||||
Logger.success 1, "Firmware flashed! #{fw_file}"
|
||||
Farmbot.Logger.success 1, "Firmware flashed! #{fw_file}"
|
||||
:ok
|
||||
{_, err_code} ->
|
||||
Logger.error 1, "Failed to flash Firmware! #{fw_file} #{err_code}"
|
||||
Farmbot.Logger.error 1, "Failed to flash Firmware! #{fw_file} #{err_code}"
|
||||
Farmbot.Firmware.Utils.replace_firmware_handler(Farmbot.Firmware.StubHandler)
|
||||
:error
|
||||
end
|
|
@ -30,8 +30,8 @@ defmodule Farmbot.Firmware.Utils do
|
|||
|
||||
@doc "replace the firmware handler at runtime."
|
||||
def replace_firmware_handler(handler) do
|
||||
old = Application.get_all_env(:farmbot)[:behaviour]
|
||||
old = Application.get_all_env(:farmbot_core)[:behaviour]
|
||||
new = Keyword.put(old, :firmware_handler, handler)
|
||||
Application.put_env(:farmbot, :behaviour, new)
|
||||
Application.put_env(:farmbot_core, :behaviour, new)
|
||||
end
|
||||
end
|
|
@ -1,5 +1,6 @@
|
|||
defmodule Farmbot.Firmware.Vec3 do
|
||||
@moduledoc "A three position vector."
|
||||
alias Farmbot.Firmware.Vec3
|
||||
|
||||
defstruct [x: -1.0, y: -1.0, z: -1.0]
|
||||
|
||||
|
@ -8,6 +9,10 @@ defmodule Farmbot.Firmware.Vec3 do
|
|||
|
||||
@typedoc @moduledoc
|
||||
@type t :: %__MODULE__{x: number, y: number, z: number}
|
||||
|
||||
def new(x, y, z) do
|
||||
%Vec3{x: x, y: y, z: z}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Inspect, for: Farmbot.Firmware.Vec3 do
|
|
@ -1,6 +1,6 @@
|
|||
defmodule Farmbot.Leds do
|
||||
@moduledoc "API for controling Farmbot LEDS."
|
||||
@led_handler Application.get_env(:farmbot, :behaviour)[:leds_handler]
|
||||
@led_handler Application.get_env(:farmbot_core, :behaviour)[:leds_handler]
|
||||
@led_handler || Mix.raise("You forgot a led handler!")
|
||||
|
||||
@valid_status [:off, :solid, :slow_blink, :fast_blink]
|
|
@ -0,0 +1,35 @@
|
|||
defmodule Farmbot.Leds.StubHandler do
|
||||
@moduledoc false
|
||||
@behaviour Farmbot.Leds.Handler
|
||||
|
||||
def red(status), do: do_debug(:red, status)
|
||||
def blue(status), do: do_debug(:blue, status)
|
||||
def green(status), do: do_debug(:green, status)
|
||||
def yellow(status), do: do_debug(:yellow, status)
|
||||
def white1(status), do: do_debug(:white, status)
|
||||
def white2(status), do: do_debug(:white, status)
|
||||
def white3(status), do: do_debug(:white, status)
|
||||
def white4(status), do: do_debug(:white, status)
|
||||
def white5(status), do: do_debug(:white, status)
|
||||
|
||||
defp do_debug(color, status) do
|
||||
msg = [IO.ANSI.reset(), "LED STATUS: ",
|
||||
apply(IO.ANSI, color, []),
|
||||
status_in(status),
|
||||
to_string(color),
|
||||
" ",
|
||||
to_string(status),
|
||||
status_out(status),
|
||||
IO.ANSI.reset()
|
||||
]
|
||||
IO.puts(msg)
|
||||
end
|
||||
|
||||
defp status_in(:slow_blink), do: IO.ANSI.blink_slow()
|
||||
defp status_in(:fast_blink), do: IO.ANSI.blink_rapid()
|
||||
defp status_in(_), do: ""
|
||||
|
||||
defp status_out(:slow_blink), do: IO.ANSI.blink_off()
|
||||
defp status_out(:fast_blink), do: IO.ANSI.blink_off()
|
||||
defp status_out(_), do: ""
|
||||
end
|
|
@ -0,0 +1,100 @@
|
|||
defmodule Farmbot.Log do
|
||||
@moduledoc """
|
||||
This is _not_ the same as the API's log asset.
|
||||
"""
|
||||
|
||||
defmodule LogLevelType do
|
||||
@moduledoc false
|
||||
@level_atoms [:debug, :info, :error, :warn, :busy, :success, :fun]
|
||||
@level_strs ["debug", "info", "error", "warn", "busy", "success", "fun"]
|
||||
|
||||
def type, do: :string
|
||||
|
||||
def cast(level) when level in @level_strs, do: {:ok, level}
|
||||
def cast(level) when level in @level_atoms, do: {:ok, to_string(level)}
|
||||
def cast(_), do: :error
|
||||
|
||||
def load(str), do: {:ok, String.to_existing_atom(str)}
|
||||
def dump(str), do: {:ok, to_string(str)}
|
||||
end
|
||||
|
||||
defmodule VersionType do
|
||||
@moduledoc false
|
||||
|
||||
def type, do: :string
|
||||
|
||||
def cast(%Version{} = version), do: {:ok, to_string(version)}
|
||||
def cast(str), do: {:ok, str}
|
||||
|
||||
def load(str), do: Version.parse(str)
|
||||
def dump(str), do: {:ok, to_string(str)}
|
||||
end
|
||||
|
||||
defmodule AtomType do
|
||||
@moduledoc false
|
||||
|
||||
def type, do: :string
|
||||
|
||||
def cast(atom) when is_atom(atom), do: {:ok, to_string(atom)}
|
||||
def cast(str), do: {:ok, str}
|
||||
|
||||
def load(str), do: {:ok, String.to_atom(str)}
|
||||
def dump(str), do: {:ok, to_string(str)}
|
||||
end
|
||||
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
schema "logs" do
|
||||
field(:level, LogLevelType)
|
||||
field(:verbosity, :integer)
|
||||
field(:message, :string)
|
||||
field(:meta, Farmbot.EctoTypes.TermType)
|
||||
field(:function, :string)
|
||||
field(:file, :string)
|
||||
field(:line, :integer)
|
||||
field(:module, AtomType)
|
||||
field(:version, VersionType)
|
||||
field(:commit, :string)
|
||||
field(:target, :string)
|
||||
field(:env, :string)
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@required_fields [:level, :verbosity, :message]
|
||||
@optional_fields [:meta, :function, :file, :line, :module]
|
||||
|
||||
def changeset(log, params \\ %{}) do
|
||||
log
|
||||
|> new()
|
||||
|> cast(params, @required_fields ++ @optional_fields)
|
||||
|> validate_required(@required_fields)
|
||||
end
|
||||
|
||||
def new(%Farmbot.Log{} = merge) do
|
||||
merge
|
||||
|> Map.put(:version, Version.parse!(Farmbot.Project.version()))
|
||||
|> Map.put(:commit, to_string(Farmbot.Project.commit()))
|
||||
|> Map.put(:target, to_string(Farmbot.Project.target()))
|
||||
|> Map.put(:env, to_string(Farmbot.Project.env()))
|
||||
end
|
||||
|
||||
defimpl String.Chars, for: Farmbot.Log do
|
||||
def to_string(log) do
|
||||
if log.meta[:color] && function_exported?(IO.ANSI, log.meta[:color], 0) do
|
||||
"#{apply(IO.ANSI, log.meta[:color], [])}#{log.message}#{color(:normal)}\n"
|
||||
else
|
||||
"#{color(log.level)}#{log.message}#{color(:normal)}\n"
|
||||
end
|
||||
end
|
||||
|
||||
defp color(:debug), do: IO.ANSI.light_blue()
|
||||
defp color(:info), do: IO.ANSI.cyan()
|
||||
defp color(:busy), do: IO.ANSI.blue()
|
||||
defp color(:success), do: IO.ANSI.green()
|
||||
defp color(:warn), do: IO.ANSI.yellow()
|
||||
defp color(:error), do: IO.ANSI.red()
|
||||
defp color(:normal), do: IO.ANSI.normal()
|
||||
defp color(_), do: IO.ANSI.normal()
|
||||
end
|
||||
end
|
|
@ -2,41 +2,8 @@ defmodule Farmbot.Logger do
|
|||
@moduledoc """
|
||||
Log messages to Farmot endpoints.
|
||||
"""
|
||||
use GenStage
|
||||
|
||||
def how_many_logs do
|
||||
alias IO.ANSI
|
||||
|
||||
count = LoggerBackendSqlite.all_logs() |> Enum.count()
|
||||
size_mb = LoggerBackendSqlite.stat().size * 1.0e-6
|
||||
total = Application.get_env(:logger, LoggerBackendSqlite)[:max_logs]
|
||||
|
||||
IO.puts [
|
||||
ANSI.clear(), ANSI.home(), ANSI.blue(),
|
||||
"logs: ", ANSI.green(), to_string(count), " / ", to_string(total),
|
||||
|
||||
"\r\n", ANSI.blue(),
|
||||
"size: ", ANSI.green(), to_string(size_mb),
|
||||
|
||||
"\r\n", ANSI.blue(), "\r\n",
|
||||
"prediction: ", ANSI.green(), to_string(total / count),
|
||||
"\r\n", ANSI.blue(),
|
||||
|
||||
"size: ", ANSI.green(), to_string((total / count) * size_mb),
|
||||
"\r\n", ANSI.normal()
|
||||
]
|
||||
end
|
||||
|
||||
def format_logs do
|
||||
RingLogger.get()
|
||||
|> Enum.map(fn({level, {_logger, message, timestamp_tup, _meta}}) ->
|
||||
# {{year, month, day}, {hour, minute, second, _}} = timestamp_tup
|
||||
timestamp = Timex.to_datetime(timestamp_tup) |> DateTime.to_iso8601()
|
||||
reg = ~r/\x1B\[[0-?]*[ -\/]*[@-~]/
|
||||
|
||||
"[#{level} #{timestamp}] - #{Regex.replace(reg, to_string(message), "")}"
|
||||
end)
|
||||
end
|
||||
alias Farmbot.Logger.Repo
|
||||
|
||||
@doc "Send a debug message to log endpoints"
|
||||
defmacro debug(verbosity, message, meta \\ []) do
|
||||
|
@ -87,30 +54,25 @@ defmodule Farmbot.Logger do
|
|||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
alias Farmbot.Logger
|
||||
import Farmbot.Logger, only: [
|
||||
debug: 3,
|
||||
debug: 2,
|
||||
info: 3,
|
||||
info: 2,
|
||||
busy: 3,
|
||||
busy: 2,
|
||||
success: 3,
|
||||
success: 2,
|
||||
warn: 3,
|
||||
warn: 2,
|
||||
error: 3,
|
||||
error: 2,
|
||||
fun: 2,
|
||||
fun: 3
|
||||
]
|
||||
def insert_log!(%Farmbot.Log{} = log) do
|
||||
Farmbot.Log.changeset(log, %{})
|
||||
|> Repo.insert!()
|
||||
end
|
||||
|
||||
@doc "Gets a log by it's id, deletes it."
|
||||
def handle_log(id) do
|
||||
case Repo.get(Farmbot.Log, id) do
|
||||
%Farmbot.Log{} = log -> Repo.delete!(log)
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Gets all available logs and deletes them."
|
||||
def handle_all_logs do
|
||||
Repo.all(Farmbot.Log)
|
||||
|> Enum.map(&Repo.delete!(&1))
|
||||
end
|
||||
|
||||
@doc false
|
||||
def dispatch_log(%Macro.Env{} = env, level, verbosity, message, meta)
|
||||
when level in [:info, :debug, :busy, :warn, :success, :error, :fun]
|
||||
|
@ -118,71 +80,57 @@ defmodule Farmbot.Logger do
|
|||
and is_binary(message)
|
||||
and is_list(meta)
|
||||
do
|
||||
GenStage.cast(__MODULE__, {:dispatch_log, {env, level, verbosity, message, meta}})
|
||||
end
|
||||
|
||||
def dispatch_log(%Farmbot.Log{} = log) do
|
||||
GenStage.cast(__MODULE__, {:dispatch_log, log})
|
||||
end
|
||||
|
||||
@doc false
|
||||
def start_link() do
|
||||
GenStage.start_link(__MODULE__, [], [name: __MODULE__])
|
||||
end
|
||||
|
||||
def init([]) do
|
||||
espeak = System.find_executable("espeak")
|
||||
{:producer, %{espeak: espeak}, dispatcher: GenStage.BroadcastDispatcher}
|
||||
end
|
||||
|
||||
def handle_demand(_, state) do
|
||||
{:noreply, [], state}
|
||||
end
|
||||
|
||||
def handle_events(_, _from, state) do
|
||||
{:noreply, [], state}
|
||||
end
|
||||
|
||||
def handle_cast({:dispatch_log, {env, level, verbosity, message, meta}}, state) do
|
||||
time = :os.system_time(:seconds)
|
||||
fun = case env.function do
|
||||
{fun, ar} -> "#{fun}/#{ar}"
|
||||
nil -> "no_function"
|
||||
end
|
||||
meta_map = Map.new(meta)
|
||||
maybe_espeak(message, Map.get(meta_map, :channels, []), state.espeak)
|
||||
log = struct(Farmbot.Log, [
|
||||
time: time,
|
||||
|
||||
struct(Farmbot.Log, [
|
||||
level: level,
|
||||
verbosity: verbosity,
|
||||
message: message,
|
||||
meta: meta_map,
|
||||
meta: Map.new(meta),
|
||||
function: fun,
|
||||
file: env.file,
|
||||
line: env.line,
|
||||
module: env.module])
|
||||
|> dispatch_log()
|
||||
end
|
||||
|
||||
@doc false
|
||||
def dispatch_log(%Farmbot.Log{} = log) do
|
||||
log
|
||||
|> insert_log!()
|
||||
|> elixir_log()
|
||||
|> fn(log) ->
|
||||
Farmbot.Registry.dispatch(__MODULE__, {:log_ready, log.id})
|
||||
end.()
|
||||
end
|
||||
|
||||
defp elixir_log(%Farmbot.Log{} = log) do
|
||||
# TODO Connor - fix time
|
||||
logger_meta = [
|
||||
application: :farmbot,
|
||||
function: fun,
|
||||
file: env.file,
|
||||
line: env.line,
|
||||
module: env.module,
|
||||
time: time
|
||||
function: log.function,
|
||||
file: log.file,
|
||||
line: log.line,
|
||||
module: log.module,
|
||||
# time: time
|
||||
]
|
||||
level = log.level
|
||||
logger_level = if level in [:info, :debug, :warn, :error], do: level, else: :info
|
||||
Elixir.Logger.bare_log(logger_level, log, logger_meta)
|
||||
{:noreply, [log], state}
|
||||
log
|
||||
end
|
||||
|
||||
def handle_cast({:dispatch_log, %Farmbot.Log{} = log}, state) do
|
||||
{:noreply, [log], state}
|
||||
@doc "Helper function for deciding if a message should be logged or not."
|
||||
def should_log?(module, verbosity)
|
||||
def should_log?(nil, verbosity) when verbosity <= 3, do: true
|
||||
def should_log?(nil, _), do: false
|
||||
|
||||
def should_log?(module, verbosity) when verbosity <= 3 do
|
||||
List.first(Module.split(module)) == "Farmbot"
|
||||
end
|
||||
|
||||
defp maybe_espeak(_message, _channels, nil), do: :ok
|
||||
defp maybe_espeak(message, channels, exe) do
|
||||
if Enum.find(channels, fn(ch) -> (ch == :espeak) || (ch == "espeak") end) do
|
||||
spawn System, :cmd, [exe, [message]]
|
||||
end
|
||||
:ok
|
||||
end
|
||||
def should_log?(_, _), do: false
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
defmodule Farmbot.Logger.Repo do
|
||||
@moduledoc "Repo for storing logs."
|
||||
use Ecto.Repo,
|
||||
otp_app: :farmbot_core,
|
||||
adapter: Application.get_env(:farmbot_core, __MODULE__)[:adapter]
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue