Start splitting Farmbot Application Poncho Style

This begins splitting the monolithic Farmbot application
into separate, independent OTP applicatoins.
pull/974/head
connor rigby 2018-07-25 15:08:53 -07:00 committed by Connor Rigby
parent 1fe52021c7
commit cf1ef23b17
No known key found for this signature in database
GPG Key ID: 29A88B24B70456E0
515 changed files with 5004 additions and 10489 deletions

View File

@ -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`.
#
]
}
]
}

View File

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

6
.gitignore vendored
View File

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

6
.gitmodules vendored
View File

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

View File

@ -1,17 +0,0 @@
alias Farmbot.System.{
ConfigStorage
}
alias Farmbot.Asset
alias Farmbot.Asset.{
Device,
FarmEvent,
GenericPointer,
Peripheral,
Point,
Regimen,
Sensor,
Sequence,
ToolSlot,
Tool
}

View File

@ -1,2 +1,2 @@
erlang 21.2.4
elixir 1.8.1-otp-21
elixir 1.8.1-otp-21

View File

@ -1,4 +1,8 @@
# Changelog
# 8.0.0
* Reorganize project structure
# 7.0.3
* Update to AMQP to disable `heartbeat` timeouts

115
Makefile
View File

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

View File

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

47
TODO.md 100644
View File

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

View File

@ -1,2 +1 @@
7.0.3
8.0.0

View File

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

View File

@ -1,6 +0,0 @@
use Mix.Config
config :farmbot, :authorization,
email: "travis@travis.org",
password: "password123",
server: "https://staging.farmbot.io"

View File

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

View File

@ -1,2 +0,0 @@
use Mix.Config
import_config "dev.exs"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +0,0 @@
{
"skip_files": [
"test/support",
"nerves/",
"lib/mix/"
]
}

View File

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

28
farmbot_core/.gitignore vendored 100644
View File

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

View File

@ -0,0 +1 @@
../.tool-versions

View File

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

View File

@ -0,0 +1,3 @@
# FarmbotCore
Core Farmbot Services.
This includes Logging, Configuration, Asset management and Firmware.

Binary file not shown.

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,4 @@
defmodule Farmbot.Asset.Repo.ModuleType.FarmEvent do
@moduledoc false
use Farmbot.Asset.Repo.ModuleType, valid_mods: ~w(Sequence Regimen)
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,8 @@
defmodule Farmbot.BotState.LocationData do
@moduledoc false
defstruct [
scaled_encoders: nil,
raw_encoders: nil,
position: nil
]
end

View File

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

View File

@ -0,0 +1,4 @@
defmodule Farmbot.BotState.Pin do
@moduledoc false
defstruct [:mode, :value]
end

View File

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

View File

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

View File

@ -0,0 +1,3 @@
defmodule Farmbot.CeleryScript.IOLayer do
@callback handle_io(Csvm.AST.t()) :: {:ok, Csvm.AST.t()} | :ok | {:error, String.t()}
end

View File

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

View File

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

View File

@ -1,4 +1,4 @@
defmodule Farmbot.System.ConfigStorage.BoolValue do
defmodule Farmbot.Config.BoolValue do
@moduledoc false
use Ecto.Schema

View File

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

View File

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

View File

@ -1,4 +1,4 @@
defmodule Farmbot.System.ConfigStorage.FloatValue do
defmodule Farmbot.Config.FloatValue do
@moduledoc false
use Ecto.Schema

View File

@ -1,4 +1,4 @@
defmodule Farmbot.System.ConfigStorage.Group do
defmodule Farmbot.Config.Group do
@moduledoc false
use Ecto.Schema

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
defmodule Farmbot.System.ConfigStorage.StringValue do
defmodule Farmbot.Config.StringValue do
@moduledoc false
use Ecto.Schema

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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