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 *.js
/release-* /release-*
/node_modules /node_modules
/farmbot_ext
/farmbot_core
/farmbot_os
/farmbot_celery_script
/farmbot_firmware
# Various env vars. # Various env vars.
.env .env
@ -53,7 +48,6 @@ upload.sh
artifacts artifacts
RELEASE_NOTES RELEASE_NOTES
*.hex *.hex
!/priv/eeprom_clear.ino.hex
# lagger can't be disabled. # lagger can't be disabled.
/log /log
.make_state .make_state

6
.gitmodules vendored
View File

@ -1,4 +1,4 @@
[submodule "c_src/farmbot-arduino-firmware"] [submodule "farmbot_core/c_src/farmbot-arduino-firmware"]
path = c_src/farmbot-arduino-firmware path = farmbot_core/c_src/farmbot-arduino-firmware
url = https://github.com/FarmBot/farmbot-arduino-firmware.git url = git@github.com:Farmbot/Farmbot-Arduino-Firmware.git
ignore = dirty 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 erlang 21.2.4
elixir 1.8.1-otp-21 elixir 1.8.1-otp-21

View File

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

115
Makefile
View File

@ -1,82 +1,65 @@
ALL := .PHONY: all clean
CLEAN := .DEFAULT_GOAL: all
PREFIX = $(MIX_COMPILE_PATH)/../priv MIX_ENV := $(MIX_ENV)
BUILD = $(MIX_COMPILE_PATH)/../obj MIX_TARGET := $(MIX_TARGET)
SLACK_CHANNEL := $(SLACK_CHANNEL)
# Set Erlang-specific compile and linker flags ifeq ($(MIX_ENV),)
ERL_CFLAGS ?= -I$(ERL_EI_INCLUDE_DIR) MIX_ENV := dev
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)
endif endif
ESQLITE_SRC = $(MIX_DEPS_PATH)/esqlite/c_src ifeq ($(MIX_TARGET),)
ESQLITE_BUILD = $(MIX_BUILD_PATH)/lib/esqlite/obj MIX_TARGET := host
ESQLITE_PREFIX = $(MIX_BUILD_PATH)/lib/esqlite/priv endif
.PHONY: fbos_arduino_firmware fbos_clean_arduino_firmware all clean all: help
SQLITE_CFLAGS := -DSQLITE_THREADSAFE=1 \ help:
-DSQLITE_USE_URI \ @echo "no"
-DSQLITE_ENABLE_FTS3 \
-DSQLITE_ENABLE_FTS3_PARENTHESIS
all: $(PREFIX) \ farmbot_core_clean:
$(BUILD) \ cd farmbot_core && \
$(PREFIX)/build_calendar.so \ make clean && \
$(ESQLITE_BUILD) \ rm -rf priv/*.hex &&\
$(ESQLITE_PREFIX) \ rm -rf priv/*.so &&\
$(ESQLITE_PREFIX)/esqlite3_nif.so rm -rf ./.*.sqlite3 &&\
rm -rf _build deps
clean: farmbot_ext_clean:
$(RM) $(PREFIX)/*.so cd farmbot_ext && \
$(RM) $(ESQLITE_PREFIX)/*.so rm -rf ./.*.sqlite3 &&\
rm -rf _build deps
## ARDUINO FIRMWARE farmbot_os_clean:
cd farmbot_os && \
rm -rf _build deps
fbos_arduino_firmware: clean: farmbot_core_clean farmbot_ext_clean farmbot_os_clean
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
fbos_clean_arduino_firmware: farmbot_core_test:
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 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 farmbot_os_test:
$(CC) -o $@ $(ERL_LDFLAGS) $(LDFLAGS) $^ 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 test: farmbot_core_test farmbot_ext_test farmbot_os_test
$(CC) -c $(ERL_CFLAGS) $(CFLAGS) $(SQLITE_CFLAGS) -o $@ $<
$(ESQLITE_BUILD)/queue.o: $(ESQLITE_SRC)/queue.c farmbot_os_firmware:
$(CC) -c $(ERL_CFLAGS) $(CFLAGS) $(SQLITE_CFLAGS) -o $@ $< cd farmbot_os && \
MIX_ENV=$(MIX_ENV) MIX_TARGET=$(MIX_TARGET) mix do deps.get, firmware
$(ESQLITE_BUILD)/sqlite3.o: $(ESQLITE_SRC)/sqlite3.c farmbot_os_firmware_slack: farmbot_os_firmware
$(CC) -c $(CFLAGS) $(SQLITE_CFLAGS) -o $@ $< cd farmbot_os && \
MIX_ENV=$(MIX_ENV) MIX_TARGET=$(MIX_TARGET) mix farmbot.firmware.slack --channels $(SLACK_CHANNEL)
## 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)

View File

@ -1,7 +1,7 @@
# Build status # Build status
| Master Build Status | Staging Build Status | Beta 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 # FarmBot OS
@ -47,7 +47,7 @@ _Refer to the [software documentation Configurator page](https://software.farm.b
## Problems? ## Problems?
See the [FAQ](docs/FAQ.md) 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? ## Security Concerns?
@ -55,5 +55,5 @@ We take security seriously and value the input of independent researchers. Pleas
## Want to Help? ## 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) [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. problem probably higher up the stack.
""" """
alias Farmbot.Asset.Device
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
@ -14,7 +15,7 @@ defmodule Farmbot.Asset.Device do
@required_fields [:id, :name] @required_fields [:id, :name]
def changeset(device, params \\ %{}) do def changeset(%Device{} = device, params \\ %{}) do
device device
|> cast(params, @required_fields) |> cast(params, @required_fields)
|> validate_required(@required_fields) |> validate_required(@required_fields)

View File

@ -9,29 +9,34 @@ defmodule Farmbot.Asset.FarmEvent do
@on_load :load_nif @on_load :load_nif
def load_nif do 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 case :erlang.load_nif(nif_file, 0) do
:ok -> :ok :ok -> :ok
{:error, {:reload, _}} -> :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
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 use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
use Farmbot.Logger require Farmbot.Logger
schema "farm_events" do schema "farm_events" do
field(:start_time, :string) field(:start_time, :string)
field(:end_time, :string) field(:end_time, :string)
field(:repeat, :integer) field(:repeat, :integer)
field(:time_unit, :string) field(:time_unit, :string)
field(:executable_type, Farmbot.Repo.ModuleType.FarmEvent) field(:executable_type, ModuleType.FarmEvent)
field(:executable_id, :integer) field(:executable_id, :integer)
field(:calendar, JSONType) field(:calendar, TermType)
end end
@required_fields [ @required_fields [
@ -44,7 +49,7 @@ defmodule Farmbot.Asset.FarmEvent do
:executable_id :executable_id
] ]
def changeset(farm_event, params \\ %{}) do def changeset(%FarmEvent{} = farm_event, params \\ %{}) do
farm_event farm_event
|> build_calendar |> build_calendar
|> cast(params, @required_fields ++ [:calendar]) |> cast(params, @required_fields ++ [:calendar])
@ -53,16 +58,15 @@ defmodule Farmbot.Asset.FarmEvent do
end end
@compile {:inline, [build_calendar: 1]} @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 do: fe
def build_calendar(%__MODULE__{calendar: nil} = fe), def build_calendar(%FarmEvent{calendar: nil} = fe),
do: build_calendar(%{fe | calendar: []}) do: build_calendar(%{fe | calendar: []})
def build_calendar(%__MODULE__{time_unit: "never"} = fe), def build_calendar(%FarmEvent{time_unit: "never"} = fe), do: %{fe | calendar: [fe.start_time]}
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 when is_list(calendar) do
current_time_seconds = :os.system_time(:second) current_time_seconds = :os.system_time(:second)
@ -71,7 +75,8 @@ defmodule Farmbot.Asset.FarmEvent do
|> elem(1) |> elem(1)
|> DateTime.to_unix(:second) |> 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 = fe.repeat
repeat_frequency_seconds = time_unit_to_seconds(fe.time_unit) repeat_frequency_seconds = time_unit_to_seconds(fe.time_unit)
@ -98,7 +103,7 @@ defmodule Farmbot.Asset.FarmEvent do
repeat, repeat,
repeat_frequency_seconds repeat_frequency_seconds
) do ) 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 grace_period_cutoff_seconds = now_seconds - 60
Range.new(start_time_seconds, end_time_seconds) 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. Peripherals are descriptors for pins/modes.
""" """
alias Farmbot.Asset.Peripheral
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
@ -14,7 +15,7 @@ defmodule Farmbot.Asset.Peripheral do
@required_fields [:id, :pin, :mode, :label] @required_fields [:id, :pin, :mode, :label]
def changeset(peripheral, params \\ %{}) do def changeset(%Peripheral{} = peripheral, params \\ %{}) do
peripheral peripheral
|> cast(params, @required_fields) |> cast(params, @required_fields)
|> validate_required(@required_fields) |> validate_required(@required_fields)

View File

@ -1,17 +1,19 @@
defmodule Farmbot.System.ConfigStorage.PersistentRegimen do defmodule Farmbot.Asset.PersistentRegimen do
@moduledoc """ @moduledoc """
A persistent regimen is a join between a started farm event and the regimen 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, it is set to operate on. These are stored in the database to persist reboots,
crashes etc. crashes etc.
""" """
alias Farmbot.Asset.PersistentRegimen
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Farmbot.System.ConfigStorage.PersistentRegimen
schema "persistent_regimens" do schema "persistent_regimens" do
field :regimen_id, :integer field :regimen_id, :integer
field :farm_event_id, :integer field :farm_event_id, :integer
field :time, :utc_datetime field :time, :utc_datetime
timestamps()
end end
@required_fields [:regimen_id, :farm_event_id, :time] @required_fields [:regimen_id, :farm_event_id, :time]

View File

@ -1,24 +1,27 @@
defmodule Farmbot.Asset.Point do defmodule Farmbot.Asset.Point do
@moduledoc "A Point is a location in the planting bed as denoted by X Y and Z." @moduledoc "A Point is a location in the planting bed as denoted by X Y and Z."
alias Farmbot.Asset.Point
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Farmbot.Asset.Repo.ModuleType
alias Farmbot.EctoTypes.TermType
schema "points" do schema "points" do
field(:name, :string) field(:name, :string)
field(:tool_id, :integer) field(:tool_id, :integer)
field(:x, Farmbot.Repo.JSONFloatType) field(:x, :float)
field(:y, Farmbot.Repo.JSONFloatType) field(:y, :float)
field(:z, Farmbot.Repo.JSONFloatType) field(:z, :float)
field(:meta, Farmbot.Repo.JSONType) field(:meta, TermType)
field(:pointer_type, Farmbot.Repo.ModuleType.Point) field(:pointer_type, ModuleType.Point)
end end
@required_fields [:id, :name, :x, :y, :z, :meta, :pointer_type] @required_fields [:id, :name, :x, :y, :z, :meta, :pointer_type]
@optional_fields [:tool_id] @optional_fields [:tool_id]
def changeset(point, params \\ %{}) do def changeset(%Point{} = point, params \\ %{}) do
point %Point{} = point
|> cast(params, @required_fields ++ @optional_fields) |> cast(params, @required_fields ++ @optional_fields)
|> validate_required(@required_fields) |> validate_required(@required_fields)
end end

View File

@ -3,7 +3,10 @@ defmodule Farmbot.Asset.Regimen do
A Regimen is a schedule to run sequences on. 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 use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
@ -11,7 +14,7 @@ defmodule Farmbot.Asset.Regimen do
schema "regimens" do schema "regimens" do
field(:name, :string) field(:name, :string)
field(:farm_event_id, :integer, virtual: true) field(:farm_event_id, :integer, virtual: true)
field(:regimen_items, JSONType) field(:regimen_items, TermType)
end end
@type item :: %{ @type item :: %{
@ -27,10 +30,19 @@ defmodule Farmbot.Asset.Regimen do
@required_fields [:id, :name, :regimen_items] @required_fields [:id, :name, :regimen_items]
def changeset(farm_event, params \\ %{}) do def changeset(%Regimen{} = regimen, params \\ %{}) do
farm_event regimen
|> cast(params, @required_fields) |> cast(params, @required_fields)
|> validate_required(@required_fields) |> validate_required(@required_fields)
|> unique_constraint(:id) |> unique_constraint(:id)
end 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 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 defmodule Farmbot.Asset.Repo.Snapshot do
@moduledoc false @moduledoc "Opaque data type. Hash of the entire Repo."
alias Farmbot.Repo.Snapshot alias Farmbot.Asset.Repo.Snapshot
defmodule Diff do defmodule Diff do
@moduledoc false @moduledoc false

View File

@ -3,6 +3,7 @@ defmodule Farmbot.Asset.Sensor do
Sensors are descriptors for pins/modes. Sensors are descriptors for pins/modes.
""" """
alias Farmbot.Asset.Sensor
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
@ -14,8 +15,8 @@ defmodule Farmbot.Asset.Sensor do
@required_fields [:id, :pin, :mode, :label] @required_fields [:id, :pin, :mode, :label]
def changeset(peripheral, params \\ %{}) do def changeset(%Sensor{} = sensor, params \\ %{}) do
peripheral %Sensor{} = sensor
|> cast(params, @required_fields) |> cast(params, @required_fields)
|> validate_required(@required_fields) |> validate_required(@required_fields)
|> unique_constraint(:id) |> 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." @moduledoc "describes an update to a API resource."
alias Farmbot.Asset.SyncCmd
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Farmbot.Repo.JSONType alias Farmbot.EctoTypes.TermType
schema "sync_cmds" do schema "sync_cmds" do
field(:remote_id, :integer) field(:remote_id, :integer)
field(:kind, :string) field(:kind, :string)
field(:body, JSONType) field(:body, TermType)
timestamps() timestamps()
end end
@required_fields [:remote_id, :kind] @required_fields [:remote_id, :kind]
def changeset(%__MODULE__{} = cmd, params \\ %{}) do def changeset(%SyncCmd{} = cmd, params \\ %{}) do
cmd cmd
|> cast(params, @required_fields) |> cast(params, @required_fields)
|> validate_required(@required_fields) |> validate_required(@required_fields)

View File

@ -1,6 +1,7 @@
defmodule Farmbot.Asset.Tool do defmodule Farmbot.Asset.Tool do
@moduledoc "A Tool is an item that lives in a ToolSlot" @moduledoc "A Tool is an item that lives in a ToolSlot"
alias Farmbot.Asset.Tool
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
@ -10,8 +11,8 @@ defmodule Farmbot.Asset.Tool do
@required_fields [:id, :name] @required_fields [:id, :name]
def changeset(farm_event, params \\ %{}) do def changeset(%Tool{} = tool, params \\ %{}) do
farm_event tool
|> cast(params, @required_fields) |> cast(params, @required_fields)
|> validate_required(@required_fields) |> validate_required(@required_fields)
|> unique_constraint(:id) |> 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 @moduledoc false
use Ecto.Schema use Ecto.Schema

View File

@ -1,9 +1,9 @@
defmodule Farmbot.System.ConfigStorage.Config do defmodule Farmbot.Config.Config do
@moduledoc false @moduledoc false
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Farmbot.System.ConfigStorage.{Group, BoolValue, FloatValue, StringValue} alias Farmbot.Config.{Group, BoolValue, FloatValue, StringValue}
schema "configs" do schema "configs" do
belongs_to(:group, Group) 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 @moduledoc false
use Ecto.Schema use Ecto.Schema

View File

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

View File

@ -1,10 +1,10 @@
defmodule Farmbot.System.ConfigStorage.MigrationHelpers do defmodule Farmbot.Config.MigrationHelpers do
@moduledoc false @moduledoc false
# This is pretty bad practice, but i don't plan on really changing it at all. # This is pretty bad practice, but i don't plan on really changing it at all.
alias Farmbot.System.ConfigStorage alias Farmbot.Config
alias ConfigStorage.{Config, StringValue, BoolValue, FloatValue} alias Config.{Repo, Config, StringValue, BoolValue, FloatValue}
@auth_group_id 1 @auth_group_id 1
@hw_param_group_id 2 @hw_param_group_id 2
@ -35,7 +35,7 @@ defmodule Farmbot.System.ConfigStorage.MigrationHelpers do
value.id value.id
) )
|> Config.changeset() |> Config.changeset()
|> ConfigStorage.insert!() |> Repo.insert!()
end end
def create_value(type, val \\ nil) do def create_value(type, val \\ nil) do
@ -47,6 +47,6 @@ defmodule Farmbot.System.ConfigStorage.MigrationHelpers do
|> struct() |> struct()
|> Map.put(:value, val) |> Map.put(:value, val)
|> type.changeset() |> type.changeset()
|> ConfigStorage.insert!() |> Repo.insert!()
end end
end end

View File

@ -1,9 +1,9 @@
defmodule Farmbot.System.ConfigStorage.NetworkInterface do defmodule Farmbot.Config.NetworkInterface do
@moduledoc false @moduledoc false
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
use Farmbot.Logger require Farmbot.Logger
schema "network_interfaces" do schema "network_interfaces" do
field(:name, :string, null: false) 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 @moduledoc false
use Ecto.Schema 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 """ @moduledoc """
Custom Ecto.Type for changing a string to a module. 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 # credo:disable-for-this-file Credo.Check.Refactor.FunctionArity
use GenServer use GenServer
use Farmbot.Logger require Farmbot.Logger
alias Farmbot.FarmEvent.Execution
alias Farmbot.Asset alias Farmbot.Asset
alias Farmbot.Asset.{FarmEvent, Sequence, Regimen} alias Farmbot.Asset.{FarmEvent, Sequence, Regimen}
alias Farmbot.Repo.Registry alias Farmbot.Registry
@checkup_time 1000 @checkup_time 1000
# @checkup_time 15_000 # @checkup_time 15_000
@ -35,28 +34,28 @@ defmodule Farmbot.FarmEvent.Manager do
end end
@doc false @doc false
def start_link do def start_link(args) do
GenServer.start_link(__MODULE__, [], name: __MODULE__) GenServer.start_link(__MODULE__, args, name: __MODULE__)
end end
def init([]) do def init([]) do
Registry.subscribe() Farmbot.Registry.subscribe()
send(self(), :checkup) send(self(), :checkup)
{:ok, struct(State)} {:ok, struct(State)}
end end
def terminate(reason, _state) do def terminate(reason, _state) do
Logger.error(1, "FarmEvent Manager terminated: #{inspect(reason)}") Farmbot.Logger.error(1, "FarmEvent Manager terminated: #{inspect(reason)}")
end 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}.") maybe_farm_event_log("Starting monitor on FarmEvent: #{data.id}.")
Map.put(state.events, data.id, data) Map.put(state.events, data.id, data)
|> reindex(state) |> reindex(state)
end 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}.") maybe_farm_event_log("Destroying monitor on FarmEvent: #{data.id}.")
if String.contains?(data.executable_type, "Regimen") do if String.contains?(data.executable_type, "Regimen") do
@ -71,7 +70,7 @@ defmodule Farmbot.FarmEvent.Manager do
|> reindex(state) |> reindex(state)
end 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}.") maybe_farm_event_log("Reindexing monitor on FarmEvent: #{data.id}.")
if String.contains?(data.executable_type, "Regimen") do if String.contains?(data.executable_type, "Regimen") do
@ -89,17 +88,17 @@ defmodule Farmbot.FarmEvent.Manager do
|> reindex(state) |> reindex(state)
end 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) Farmbot.Regimen.Supervisor.stop_all_managers(data)
{:noreply, state} {:noreply, state}
end 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) Farmbot.Regimen.Supervisor.reindex_all_managers(data)
{:noreply, state} {:noreply, state}
end 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 for reg <- Farmbot.Asset.get_regimens_using_sequence(sequence.id) do
Farmbot.Regimen.Supervisor.reindex_all_managers(reg) Farmbot.Regimen.Supervisor.reindex_all_managers(reg)
end end
@ -107,7 +106,7 @@ defmodule Farmbot.FarmEvent.Manager do
{:noreply, state} {:noreply, state}
end end
def handle_info({Registry, _, _, _}, state) do def handle_info({Registry, _}, state) do
{:noreply, state} {:noreply, state}
end end
@ -122,7 +121,7 @@ defmodule Farmbot.FarmEvent.Manager do
end end
def handle_info({:DOWN, _, :process, _, error}, state) do 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) timer = Process.send_after(self(), :checkup, @checkup_time)
{:noreply, %{state | timer: timer, checkup: nil}} {:noreply, %{state | timer: timer, checkup: nil}}
end end
@ -162,11 +161,14 @@ defmodule Farmbot.FarmEvent.Manager do
# Map over the events for logging. # Map over the events for logging.
# Both Sequences and Regimens have a `name` field. # Both Sequences and Regimens have a `name` field.
names = Enum.map(late_executables, &Map.get(elem(&1, 0), :name)) 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) schedule_events(late_executables, now)
end 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 end
defp do_checkup(list, time, late_events \\ [], state) defp do_checkup(list, time, late_events \\ [], state)
@ -181,7 +183,8 @@ defmodule Farmbot.FarmEvent.Manager do
# update state. # update state.
new_state = %{ new_state = %{
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 case new_late_executable do
@ -263,7 +266,9 @@ defmodule Farmbot.FarmEvent.Manager do
event, event,
_now _now
) do ) 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} {nil, last_time}
end end
@ -276,9 +281,9 @@ defmodule Farmbot.FarmEvent.Manager do
_ _
) do ) do
maybe_farm_event_log( maybe_farm_event_log(
"regimen #{event.name} (#{event.id}) is not scheduled yet. (#{inspect(schedule_time)}) (#{ "regimen #{event.name} (#{event.id}) is not scheduled yet. (#{
inspect(Timex.now()) inspect(schedule_time)
})" }) (#{inspect(Timex.now())})"
) )
{nil, last_time} {nil, last_time}
@ -317,7 +322,8 @@ defmodule Farmbot.FarmEvent.Manager do
event, event,
now now
) do ) 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 case run? do
true -> {event, next_time} true -> {event, next_time}
@ -352,7 +358,9 @@ defmodule Farmbot.FarmEvent.Manager do
event, event,
_now _now
) do ) 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} {nil, last_time}
end 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. # if there is no last time, check if time is passed now within 60 seconds.
defp should_run_sequence?([first_time | _], nil, now) do 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 # convert the first_time to a DateTime
dt = Timex.parse!(first_time, "{ISO:Extended}") 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 defp schedule_events([{executable, farm_event} | rest], now) do
# Spawn to be non blocking here. Maybe link to this process? # Spawn to be non blocking here. Maybe link to this process?
time = Timex.parse!(farm_event.start_time, "{ISO:Extended}") time = Timex.parse!(farm_event.start_time, "{ISO:Extended}")
cond do cond do
match?(%Regimen{}, executable) -> match?(%Regimen{}, executable) ->
spawn(fn -> spawn(Regimen, :schedule_event, [executable, time])
Execution.execute_event(executable, time)
end)
match?(%Sequence{}, executable) -> match?(%Sequence{}, executable) ->
spawn(fn -> Execution.execute_event(executable, now) end) spawn(Sequence, :schedule_event, [executable, time])
end end
# Continue enumeration. # Continue enumeration.
@ -452,8 +458,8 @@ defmodule Farmbot.FarmEvent.Manager do
defp get_now(), do: Timex.now() defp get_now(), do: Timex.now()
defp maybe_farm_event_log(message) do defp maybe_farm_event_log(message) do
if Application.get_env(:farmbot, :farm_event_debug_log) do if Application.get_env(:farmbot_core, :farm_event_debug_log) do
Logger.debug(3, message) Farmbot.Logger.debug(3, message)
else else
:ok :ok
end end
@ -461,6 +467,6 @@ defmodule Farmbot.FarmEvent.Manager do
@doc "Enable or disbale debug logs for farmevents." @doc "Enable or disbale debug logs for farmevents."
def debug_logs(bool \\ true) when is_boolean(bool) do 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
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 defmodule Farmbot.Firmware.CompletionLogs do
@moduledoc false @moduledoc false
use Farmbot.Logger require Farmbot.Logger
import Farmbot.System.ConfigStorage, only: [get_config_value: 3] import Farmbot.Config, only: [get_config_value: 3]
alias Farmbot.Firmware.Command alias Farmbot.Firmware.Command
def maybe_log_complete(%Command{fun: :move_absolute, args: [pos | _]}, {:error, _reason}) do def maybe_log_complete(%Command{fun: :move_absolute, args: [pos | _]}, {:error, _reason}) do
unless get_config_value(:bool, "settings", "firmware_input_log") 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
end end
@ -21,58 +21,58 @@ defmodule Farmbot.Firmware.CompletionLogs do
_ -> pos _ -> pos
end end
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 else
Logger.success 1, "Movement to #{inspect pos} complete." Farmbot.Logger.success 1, "Movement to #{inspect pos} complete."
end end
end end
end end
def maybe_log_complete(%Command{fun: :home}, {:error, _reason}) do def maybe_log_complete(%Command{fun: :home}, {:error, _reason}) do
unless get_config_value(:bool, "settings", "firmware_input_log") 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
end end
def maybe_log_complete(%Command{fun: :home_all}, _reply) do def maybe_log_complete(%Command{fun: :home_all}, _reply) do
unless get_config_value(:bool, "settings", "firmware_input_log") 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
end end
def maybe_log_complete(_command, {:error, :report_axis_home_complete_x}) do def maybe_log_complete(_command, {:error, :report_axis_home_complete_x}) do
unless get_config_value(:bool, "settings", "firmware_input_log") 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
end end
def maybe_log_complete(_command, {:error, :report_axis_home_complete_y}) do def maybe_log_complete(_command, {:error, :report_axis_home_complete_y}) do
unless get_config_value(:bool, "settings", "firmware_input_log") 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
end end
def maybe_log_complete(_command, {:error, :report_axis_home_complete_z}) do def maybe_log_complete(_command, {:error, :report_axis_home_complete_z}) do
unless get_config_value(:bool, "settings", "firmware_input_log") 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
end end
def maybe_log_complete(_command, {:error, :report_axis_timeout_x}) do def maybe_log_complete(_command, {:error, :report_axis_timeout_x}) do
unless get_config_value(:bool, "settings", "firmware_input_log") 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
end end
def maybe_log_complete(_command, {:error, :report_axis_timeout_y}) do def maybe_log_complete(_command, {:error, :report_axis_timeout_y}) do
unless get_config_value(:bool, "settings", "firmware_input_log") 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
end end
def maybe_log_complete(_command, {:error, :report_axis_timeout_z}) do def maybe_log_complete(_command, {:error, :report_axis_timeout_z}) do
unless get_config_value(:bool, "settings", "firmware_input_log") 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
end end

View File

@ -3,7 +3,7 @@ defmodule Farmbot.Firmware.EstopTimer do
Module responsible for timing emails about E stops. Module responsible for timing emails about E stops.
""" """
use GenServer use GenServer
use Farmbot.Logger require Farmbot.Logger
@msg "Farmbot has been E-Stopped for more than 10 minutes." @msg "Farmbot has been E-Stopped for more than 10 minutes."
# Ten minutes. # Ten minutes.
@ -27,8 +27,8 @@ defmodule Farmbot.Firmware.EstopTimer do
end end
@doc false @doc false
def start_link do def start_link(args) do
GenServer.start_link(__MODULE__, [], [name: __MODULE__]) GenServer.start_link(__MODULE__, args, [name: __MODULE__])
end end
@doc false @doc false
@ -59,7 +59,7 @@ defmodule Farmbot.Firmware.EstopTimer do
if state.already_sent do if state.already_sent do
{:noreply, %{state | timer: nil}} {:noreply, %{state | timer: nil}}
else else
Logger.warn 1, @msg, [channels: [:fatal_email]] Farmbot.Logger.warn 1, @msg, [channels: [:fatal_email]]
{:noreply, %{state | timer: nil, already_sent: true}} {:noreply, %{state | timer: nil, already_sent: true}}
end end
end end

View File

@ -2,12 +2,11 @@ defmodule Farmbot.Firmware do
@moduledoc "Allows communication with the firmware." @moduledoc "Allows communication with the firmware."
use GenStage use GenStage
use Farmbot.Logger require Farmbot.Logger
alias Farmbot.Bootstrap.SettingsSync
alias Farmbot.Firmware.{Command, CompletionLogs, Vec3, EstopTimer, Utils} alias Farmbot.Firmware.{Command, CompletionLogs, Vec3, EstopTimer, Utils}
import Utils import Utils
import Farmbot.System.ConfigStorage, import Farmbot.Config,
only: [get_config_value: 3, update_config_value: 4, get_config_as_map: 0] only: [get_config_value: 3, update_config_value: 4, get_config_as_map: 0]
import CompletionLogs, import CompletionLogs,
@ -109,8 +108,8 @@ defmodule Farmbot.Firmware do
end end
@doc "Start the firmware services." @doc "Start the firmware services."
def start_link do def start_link(args) do
GenStage.start_link(__MODULE__, [], name: __MODULE__) GenStage.start_link(__MODULE__, args, name: __MODULE__)
end end
## GenStage ## GenStage
@ -155,7 +154,7 @@ defmodule Farmbot.Firmware do
def init([]) do def init([]) do
handler_mod = 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 case handler_mod.start_link() do
{:ok, handler} -> {:ok, handler} ->
@ -168,7 +167,7 @@ defmodule Farmbot.Firmware do
} }
{:error, reason} -> {:error, reason} ->
replace_firmware_handler(Farmbot.Firmware.StubHandler) 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([]) init([])
end end
@ -192,7 +191,7 @@ defmodule Farmbot.Firmware do
end end
def handle_info({:EXIT, _, reason}, state) do 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 case state.handler_mod.start_link() do
{:ok, handler} -> {:ok, handler} ->
new_state = %{state | handler: handler} new_state = %{state | handler: handler}
@ -208,7 +207,7 @@ defmodule Farmbot.Firmware do
case state.current do case state.current do
# Check if this timeout is actually talking about the current command. # Check if this timeout is actually talking about the current command.
^timeout_command = current -> ^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 case apply(state.handler_mod, current.fun, [state.handler | current.args]) do
:ok -> :ok ->
timer = start_timer(current, state.timeout_ms) 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 # If this timeout was not talking about the current command
%Command{} = current -> %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}} {:noreply, [], %{state | timer: nil}}
# If there is no current command, we got a different kind of stray. # If there is no current command, we got a different kind of stray.
@ -272,7 +271,7 @@ defmodule Farmbot.Firmware do
end end
defp do_queue_cmd(%Command{fun: _fun, args: _args, from: _from} = current, state) do 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) new_q = :queue.in(current, state.queue)
{:noreply, [], %{state | queue: new_q}} {:noreply, [], %{state | queue: new_q}}
end end
@ -306,7 +305,7 @@ defmodule Farmbot.Firmware do
defp handle_gcode({:debug_message, message}, state) do defp handle_gcode({:debug_message, message}, state) do
if get_config_value(:bool, "settings", "arduino_debug_messages") 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 end
{nil, state} {nil, state}
end end
@ -314,7 +313,7 @@ defmodule Farmbot.Firmware do
defp handle_gcode(code, state) when code in [:error, :invalid_command] do defp handle_gcode(code, state) when code in [:error, :invalid_command] do
maybe_cancel_timer(state.timer, state.current) maybe_cancel_timer(state.timer, state.current)
if state.current do 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}) do_reply(state, {:error, :firmware_error})
{nil, %{state | current: nil}} {nil, %{state | current: nil}}
else else
@ -323,47 +322,46 @@ defmodule Farmbot.Firmware do
end end
defp handle_gcode(:report_no_config, state) do 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"] old = get_config_as_map()["hardware_params"]
spawn __MODULE__, :do_read_params, [Map.delete(old, "param_version")] spawn __MODULE__, :do_read_params, [Map.delete(old, "param_version")]
{nil, %{state | initialized: false, initializing: true}} {nil, %{state | initialized: false, initializing: true}}
end end
defp handle_gcode(:report_params_complete, state) do 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}} {nil, %{state | initialized: true, initializing: false}}
end end
defp handle_gcode(:idle, %{initialized: false, initializing: false} = state) do 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} {nil, state}
end end
defp handle_gcode(:idle, %{initialized: true, initializing: false, current: nil, z_needs_home_on_boot: true} = state) do 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] spawn __MODULE__, :find_home, [:z]
{nil, %{state | z_needs_home_on_boot: false}} {nil, %{state | z_needs_home_on_boot: false}}
end end
defp handle_gcode(:idle, %{initialized: true, initializing: false, current: nil, y_needs_home_on_boot: true} = state) do 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] spawn __MODULE__, :find_home, [:y]
{nil, %{state | y_needs_home_on_boot: false}} {nil, %{state | y_needs_home_on_boot: false}}
end end
defp handle_gcode(:idle, %{initialized: true, initializing: false, current: nil, x_needs_home_on_boot: true} = state) do 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] spawn __MODULE__, :find_home, [:x]
{nil, %{state | x_needs_home_on_boot: false}} {nil, %{state | x_needs_home_on_boot: false}}
end end
defp handle_gcode(:idle, state) do defp handle_gcode(:idle, state) do
maybe_cancel_timer(state.timer, state.current) maybe_cancel_timer(state.timer, state.current)
Farmbot.BotState.set_busy(false)
if state.current do if state.current do
Logger.warn 1, "Got idle while executing a command." Farmbot.Logger.warn 1, "Got idle while executing a command."
timer = start_timer(state.current, state.timeout_ms) timer = start_timer(state.current, state.timeout_ms)
{nil, %{state | timer: timer}} {nil, %{state | timer: timer}}
else else
{:informational_settings, %{busy: false, locked: false}, %{state | idle: true}} {:informational_settings, %{busy: false, locked: false}, %{state | idle: true}}
end end
@ -388,7 +386,7 @@ defmodule Farmbot.Firmware do
end end
defp handle_gcode({:report_pin_mode, pin, mode_atom}, state) do 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) mode = extract_pin_mode(mode_atom)
case state.pins[pin] do case state.pins[pin] do
%{mode: _, value: _} = pin_map -> %{mode: _, value: _} = pin_map ->
@ -399,7 +397,7 @@ defmodule Farmbot.Firmware do
end end
defp handle_gcode({:report_pin_value, pin, value}, state) do 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 case state.pins[pin] do
%{mode: _, value: _} = pin_map -> %{mode: _, value: _} = pin_map ->
{:pins, %{pin => %{pin_map | value: value}}, %{state | pins: %{state.pins | pin => %{pin_map | value: value}}}} {:pins, %{pin => %{pin_map | value: value}}, %{state | pins: %{state.pins | pin => %{pin_map | value: value}}}}
@ -409,18 +407,18 @@ defmodule Farmbot.Firmware do
end end
defp handle_gcode({:report_parameter_value, param, value}, state) when (value == -1) do 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)}} {:mcu_params, %{param => nil}, %{state | params: Map.put(state.params, param, value)}}
end end
defp handle_gcode({:report_parameter_value, param, value}, state) when is_number(value) do 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)}} {:mcu_params, %{param => value}, %{state | params: Map.put(state.params, param, value)}}
end end
defp handle_gcode({:report_software_version, version}, state) do defp handle_gcode({:report_software_version, version}, state) do
hw = get_config_value(:string, "settings", "firmware_hardware") 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 case String.last(version) do
"F" -> "F" ->
update_config_value(:string, "settings", "firmware_hardware", "farmduino") update_config_value(:string, "settings", "firmware_hardware", "farmduino")
@ -476,7 +474,6 @@ defmodule Farmbot.Firmware do
end end
defp handle_gcode(:busy, state) do defp handle_gcode(:busy, state) do
Farmbot.BotState.set_busy(true)
maybe_cancel_timer(state.timer, state.current) maybe_cancel_timer(state.timer, state.current)
timer = if state.current do timer = if state.current do
start_timer(state.current, state.timeout_ms) start_timer(state.current, state.timeout_ms)
@ -488,12 +485,11 @@ defmodule Farmbot.Firmware do
defp handle_gcode(:done, state) do defp handle_gcode(:done, state) do
maybe_cancel_timer(state.timer, state.current) maybe_cancel_timer(state.timer, state.current)
Farmbot.BotState.set_busy(false)
if state.current do if state.current do
do_reply(state, :ok) do_reply(state, :ok)
{nil, %{state | current: nil}} {:informational_settings, %{busy: true}, %{state | current: nil}}
else else
{nil, state} {:informational_settings, %{busy: true}, state}
end end
end end
@ -505,7 +501,7 @@ defmodule Farmbot.Firmware do
defp handle_gcode({:report_calibration, axis, status}, state) do defp handle_gcode({:report_calibration, axis, status}, state) do
maybe_cancel_timer(state.timer, state.current) maybe_cancel_timer(state.timer, state.current)
Logger.busy 1, "Axis #{axis} calibration: #{status}" Farmbot.Logger.busy 1, "Axis #{axis} calibration: #{status}"
{nil, state} {nil, state}
end end
@ -527,7 +523,7 @@ defmodule Farmbot.Firmware do
end end
defp handle_gcode(code, state) do defp handle_gcode(code, state) do
Logger.warn(3, "unhandled code: #{inspect(code)}") Farmbot.Logger.warn(3, "unhandled code: #{inspect(code)}")
{nil, state} {nil, state}
end end
@ -540,11 +536,14 @@ defmodule Farmbot.Firmware do
:ok :ok
end 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 defp maybe_update_param_from_report(param, val) when is_binary(param) do
real_val = if val, do: (val / 1), else: nil 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) update_config_value(:float, "hardware_params", to_string(param), real_val)
real_val
end end
@doc false @doc false
@ -554,7 +553,8 @@ defmodule Farmbot.Firmware do
(float_val == -1) -> :ok (float_val == -1) -> :ok
is_nil(float_val) -> :ok is_nil(float_val) -> :ok
is_number(float_val) -> is_number(float_val) ->
:ok = update_param(:"#{key}", float_val / 1) val = round(float_val)
:ok = update_param(:"#{key}", val)
end end
end end
:ok = update_param(:param_use_eeprom, 0) :ok = update_param(:param_use_eeprom, 0)
@ -576,13 +576,14 @@ defmodule Farmbot.Firmware do
str_param = to_string(param) str_param = to_string(param)
case get_config_value(:float, "hardware_params", str_param) do case get_config_value(:float, "hardware_params", str_param) do
^val -> ^val ->
Logger.success 1, "Calibrated #{param}: #{val}" Farmbot.Logger.success 1, "Calibrated #{param}: #{val}"
SettingsSync.upload_fw_kv(str_param, val) # SettingsSync.upload_fw_kv(str_param, val)
raise("fixme")
:ok :ok
_ -> report_calibration_callback(tries - 1, param, val) _ -> report_calibration_callback(tries - 1, param, val)
end end
{:error, reason} -> {: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) report_calibration_callback(tries - 1, param, val)
end end
end end
@ -598,10 +599,10 @@ defmodule Farmbot.Firmware do
%Command{fun: :emergency_lock, from: from} -> %Command{fun: :emergency_lock, from: from} ->
:ok = GenServer.reply from, {:error, :emergency_lock} :ok = GenServer.reply from, {:error, :emergency_lock}
%Command{fun: _fun, from: from} -> %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 :ok = GenServer.reply from, reply
nil -> 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 :error
end end
end end
@ -613,8 +614,4 @@ defmodule Farmbot.Firmware do
end end
end end
end end
defp start_timer(%Command{} = command, timeout) do
Process.send_after(self(), {:command_timeout, command}, timeout)
end
end end

View File

@ -75,7 +75,7 @@ defmodule Farmbot.Firmware.Gcode.Parser do
end end
@spec parse_report_calibration(binary) @spec parse_report_calibration(binary)
:: {binary, {:report_calibration, binary, binary}} :: {binary, {:report_calibration, binary, :idle | :home | :end}}
defp parse_report_calibration(r) do defp parse_report_calibration(r) do
[axis_and_status | [q]] = String.split(r, " Q") [axis_and_status | [q]] = String.split(r, " Q")
<<a::size(8), b::size(8)>> = axis_and_status <<a::size(8), b::size(8)>> = axis_and_status
@ -118,7 +118,7 @@ defmodule Farmbot.Firmware.Gcode.Parser do
| :report_encoder_position_raw | :report_encoder_position_raw
@spec report_xyz(binary, reporter) @spec report_xyz(binary, reporter)
:: {binary, {reporter, binary, binary, binary}} :: {binary, {reporter, float(), float(), float()}}
defp report_xyz(position, reporter) when is_bitstring(position), defp report_xyz(position, reporter) when is_bitstring(position),
do: position |> String.split(" ") |> do_parse_pos(reporter) do: position |> String.split(" ") |> do_parse_pos(reporter)
@ -146,8 +146,7 @@ defmodule Farmbot.Firmware.Gcode.Parser do
@doc false @doc false
@spec parse_end_stops(binary) @spec parse_end_stops(binary)
:: {:report_end_stops, :: {binary(), {:report_end_stops, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1, 0 | 1}}
binary, binary, binary, binary, binary, binary, binary}
def parse_end_stops( def parse_end_stops(
<<"XA", xa::size(8), 32, <<"XA", xa::size(8), 32,
"XB", xb::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(48), do: 0
defp pes(49), do: 1 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) def parse_pvq(params, :report_parameter_value)
when is_bitstring(params), when is_bitstring(params),
do: params |> String.split(" ") |> do_parse_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." @moduledoc "Stubs out firmware functionality when you don't have an arduino."
use GenStage use GenStage
use Farmbot.Logger require Farmbot.Logger
@behaviour Farmbot.Firmware.Handler @behaviour Farmbot.Firmware.Handler
alias Farmbot.Firmware.Vec3 alias Farmbot.Firmware.Vec3
@ -10,7 +10,7 @@ defmodule Farmbot.Firmware.StubHandler do
## Firmware Handler Behaviour. ## Firmware Handler Behaviour.
def start_link do def start_link do
Logger.warn(3, "Firmware is being stubbed.") Farmbot.Logger.warn(3, "Firmware is being stubbed.")
GenStage.start_link(__MODULE__, []) GenStage.start_link(__MODULE__, [])
end end
@ -110,7 +110,12 @@ defmodule Farmbot.Firmware.StubHandler do
dispatch = if state.locked? do dispatch = if state.locked? do
[:report_emergency_lock] [:report_emergency_lock]
else 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 end
{:noreply, dispatch, state} {:noreply, dispatch, state}
end end
@ -120,7 +125,7 @@ defmodule Farmbot.Firmware.StubHandler do
{:noreply, [:idle, :report_no_config, :idle], state} {:noreply, [:idle, :report_no_config, :idle], state}
end 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} {:reply, :ok, [:error], state}
end 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 Circuits.UART
alias Farmbot.Firmware.{UartHandler, StubHandler, Utils} alias Farmbot.Firmware.{UartHandler, StubHandler, Utils}
import Utils import Utils
use Farmbot.Logger require Farmbot.Logger
use GenServer
#TODO(Connor) - Maybe make this configurable? #TODO(Connor) - Maybe make this configurable?
case Farmbot.Project.target() do case Farmbot.Project.target() do
@ -34,8 +35,8 @@ defmodule Farmbot.Firmware.UartHandler.AutoDetector do
end end
@doc false @doc false
def start_link(_, _) do def start_link(args) do
GenServer.start_link(__MODULE__, []) GenServer.start_link(__MODULE__, args, [name: __MODULE__])
end end
def init([]) do def init([]) do
@ -47,12 +48,12 @@ defmodule Farmbot.Firmware.UartHandler.AutoDetector do
case auto_detect() do case auto_detect() do
[dev] -> [dev] ->
dev = "/dev/#{dev}" dev = "/dev/#{dev}"
Logger.success 3, "detected target UART: #{dev}" Farmbot.Logger.success 3, "detected target UART: #{dev}"
replace_firmware_handler(UartHandler) replace_firmware_handler(UartHandler)
Application.put_env(:farmbot, :uart_handler, tty: dev) Application.put_env(:farmbot_core, :uart_handler, tty: dev)
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) replace_firmware_handler(StubHandler)
:error :error
end end

View File

@ -1,7 +1,7 @@
defmodule Farmbot.Firmware.UartHandler.Framing do defmodule Farmbot.Firmware.UartHandler.Framing do
@behaviour Circuits.UART.Framing @behaviour Circuits.UART.Framing
import Farmbot.Firmware.Gcode.Parser import Farmbot.Firmware.Gcode.Parser
use Farmbot.Logger require Farmbot.Logger
# credo:disable-for-this-file Credo.Check.Refactor.FunctionArity # 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") separator = Keyword.get(args, :separator, "\n")
log_input = log_input =
Farmbot.System.ConfigStorage.get_config_value( Farmbot.Config.get_config_value(
:bool, :bool,
"settings", "settings",
"firmware_input_log" "firmware_input_log"
) )
log_output = log_output =
Farmbot.System.ConfigStorage.get_config_value( Farmbot.Config.get_config_value(
:bool, :bool,
"settings", "settings",
"firmware_output_log" "firmware_output_log"
@ -68,7 +68,7 @@ defmodule Farmbot.Firmware.UartHandler.Framing do
def add_framing(data, state) do def add_framing(data, state) do
# maybe log output here # maybe log output here
if state.log_output do if state.log_output do
Logger.debug(3, data) Farmbot.Logger.debug(3, data)
end end
{:ok, data <> state.separator, state} {:ok, data <> state.separator, state}
@ -179,13 +179,13 @@ defmodule Farmbot.Firmware.UartHandler.Framing do
defp do_parse_code(processed, log_input) do defp do_parse_code(processed, log_input) do
if log_input do if log_input do
Logger.debug(3, processed) Farmbot.Logger.debug(3, processed)
end end
parse_code(processed) parse_code(processed)
rescue rescue
er -> er ->
Logger.error(1, "Firmware parser error: #{Exception.message(er)}") Farmbot.Logger.error(1, "Firmware parser error: #{Exception.message(er)}")
{nil, :noop} {nil, :noop}
end end
end end

View File

@ -4,12 +4,11 @@ defmodule Farmbot.Firmware.UartHandler do
""" """
use GenStage use GenStage
alias Circuits.UART alias Nerves.UART
use Farmbot.Logger require Farmbot.Logger
alias Farmbot.System.ConfigStorage import Farmbot.Config, only: [update_config_value: 4, get_config_value: 3]
import ConfigStorage, only: [update_config_value: 4, get_config_value: 3]
alias Farmbot.Firmware alias Farmbot.Firmware
alias Firmware.{UartHandler, Utils} alias Firmware.Utils
import Utils import Utils
@behaviour Firmware.Handler @behaviour Firmware.Handler
@ -96,13 +95,13 @@ defmodule Farmbot.Firmware.UartHandler do
end end
def init([]) do def init([]) do
Logger.debug 3, "Uart handler init." Farmbot.Logger.debug 3, "Uart handler init."
# If in dev environment, # If in dev environment,
# it is expected that this be done at compile time. # it is expected that this be done at compile time.
# If in target environment, # If in target environment,
# this should be done by `Farmbot.Firmware.AutoDetector`. # this should be done by `Farmbot.Firmware.AutoDetector`.
error_msg = "Please configure uart handler!" 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 # Disable fw input logs after a reset of the
# Fw handler if they were enabled. # Fw handler if they were enabled.
@ -143,28 +142,8 @@ defmodule Farmbot.Firmware.UartHandler do
%{state | config_busy: true} %{state | config_busy: true}
end end
defp handle_config({:config, "settings", "firmware_hardware", val}, state) do defp handle_config({:config, "settings", "firmware_hardware", _val}, _state) do
if val != state.hw do raise("FIXME")
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
end end
defp handle_config(_, state) do defp handle_config(_, state) do
@ -172,7 +151,7 @@ defmodule Farmbot.Firmware.UartHandler do
end end
defp open_tty(tty, nerves \\ nil) do 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) nerves = nerves || UART.start_link |> elem(1)
Process.link(nerves) Process.link(nerves)
case UART.open(nerves, tty, [speed: 115_200, active: true]) do 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, idle_count \\ 0)
defp loop_until_idle(nerves, 2) do 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) Process.sleep(1500)
{:ok, nerves} {:ok, nerves}
end end
@ -197,18 +176,18 @@ defmodule Farmbot.Firmware.UartHandler do
defp loop_until_idle(nerves, idle_count) defp loop_until_idle(nerves, idle_count)
when is_pid(nerves) and is_number(idle_count) when is_pid(nerves) and is_number(idle_count)
do do
Logger.debug 3, "Waiting for firmware idle." Farmbot.Logger.debug 3, "Waiting for firmware idle."
receive do receive do
{:circuits_uart, _, {:error, reason}} -> {:stop, reason} {:circuits_uart, _, {:error, reason}} -> {:stop, reason}
{:circuits_uart, _, {:partial, _}} -> loop_until_idle(nerves, idle_count) {:circuits_uart, _, {:partial, _}} -> loop_until_idle(nerves, idle_count)
{:circuits_uart, _, {_, :idle}} -> loop_until_idle(nerves, idle_count + 1) {:circuits_uart, _, {_, :idle}} -> loop_until_idle(nerves, idle_count + 1)
{:circuits_uart, _, {_, {:debug_message, msg}}} -> {:circuits_uart, _, {_, {:debug_message, msg}}} ->
if String.contains?(msg, "STARTUP") do 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") UART.write(nerves, "F22 P2 V0")
{:ok, nerves} {:ok, nerves}
else 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) loop_until_idle(nerves, idle_count)
end end
{:circuits_uart, _, _msg} -> loop_until_idle(nerves, idle_count) {:circuits_uart, _, _msg} -> loop_until_idle(nerves, idle_count)
@ -227,7 +206,7 @@ defmodule Farmbot.Firmware.UartHandler do
end end
def terminate(reason, state) do 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 if state.nerves do
UART.close(state.nerves) UART.close(state.nerves)
UART.stop(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 # if there is an error, we assume something bad has happened, and we probably
# Are better off crashing here, and being restarted. # Are better off crashing here, and being restarted.
def handle_info({:circuits_uart, _, {:error, :eio}}, state) do def handle_info({:circuits_uart, _, {:error, :eio}}, state) do
Logger.error 1, "UART device removed." Farmbot.Logger.error 1, "UART device removed."
old_env = Application.get_env(:farmbot, :behaviour) old_env = Application.get_env(:farmbot_core, :behaviour)
new_env = Keyword.put(old_env, :firmware_handler, Firmware.StubHandler) 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} {:stop, {:error, :eio}, state}
end end
@ -250,20 +229,20 @@ defmodule Farmbot.Firmware.UartHandler do
# Unhandled gcodes just get ignored. # Unhandled gcodes just get ignored.
def handle_info({:circuits_uart, _, {:unhandled_gcode, code_str}}, state) do 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} {:noreply, [], state}
end end
def handle_info({:circuits_uart, _, {_, {:report_software_version, v}}}, state) do 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 if v in expected do
{:noreply, [{:report_software_version, v}], state} {:noreply, [{:report_software_version, v}], state}
else else
err = "Firmware version #{v} is not in expected versions: #{inspect expected}" err = "Firmware version #{v} is not in expected versions: #{inspect expected}"
Logger.error 1, err Farmbot.Logger.error 1, err
old_env = Application.get_env(:farmbot, :behaviour) old_env = Application.get_env(:farmbot_core, :behaviour)
new_env = Keyword.put(old_env, :firmware_handler, Firmware.StubHandler) 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} {:stop, :normal, state}
end end
end end
@ -281,7 +260,7 @@ defmodule Farmbot.Firmware.UartHandler do
:ok :ok
else else
err = "Echo #{code} does not match #{state.current_cmd} (#{distance})" err = "Echo #{code} does not match #{state.current_cmd} (#{distance})"
Logger.error 3, err Farmbot.Logger.error 3, err
end end
{:noreply, [], %{state | current_cmd: nil}} {:noreply, [], %{state | current_cmd: nil}}
end end
@ -295,12 +274,12 @@ defmodule Farmbot.Firmware.UartHandler do
end end
def handle_info({:circuits_uart, _, bin}, state) when is_binary(bin) do 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} {:noreply, [], state}
end end
defp do_write(bin, state, dispatch \\ []) do 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 case UART.write(state.nerves, bin) do
:ok -> {:reply, :ok, dispatch, %{state | current_cmd: bin}} :ok -> {:reply, :ok, dispatch, %{state | current_cmd: bin}}
err -> {:reply, err, [], %{state | current_cmd: nil}} 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) do_write("F42 P#{pin} M#{encoded_mode}", state, dispatch)
end 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) encoded_mode = extract_pin_mode(mode)
dispatch = [{:report_pin_mode, pin, mode}, {:report_pin_value, pin, value}] dispatch = [{:report_pin_mode, pin, mode}, {:report_pin_value, pin, value}]
do_write("F41 P#{pin} V#{value} M#{encoded_mode}", state, dispatch) 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) do_write("F83", state)
end 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) do_write("F61 P#{pin} V#{angle}", state)
end end

View File

@ -1,13 +1,13 @@
defmodule Farmbot.Firmware.UartHandler.Update do defmodule Farmbot.Firmware.UartHandler.Update do
@moduledoc false @moduledoc false
use Farmbot.Logger require Farmbot.Logger
@uart_speed 115_200 @uart_speed 115_200
alias Circuits.UART alias Circuits.UART
def maybe_update_firmware(hardware \\ nil) do 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 hardware = case hardware do
"farmduino" -> "F" "farmduino" -> "F"
"arduino" -> "R" "arduino" -> "R"
@ -20,7 +20,7 @@ defmodule Farmbot.Firmware.UartHandler.Update do
end end
def force_update_firmware(hardware \\ nil) do 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 hardware = case hardware do
"farmduino" -> "F" "farmduino" -> "F"
"arduino" -> "R" "arduino" -> "R"
@ -40,29 +40,29 @@ defmodule Farmbot.Firmware.UartHandler.Update do
] ]
:ok = UART.open(uart, tty, [speed: @uart_speed]) :ok = UART.open(uart, tty, [speed: @uart_speed])
:ok = UART.configure(uart, opts) :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) do_fw_loop(uart, tty, :idle, hardware)
close(uart) close(uart)
{:error, reason} -> {: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
end end
defp do_fw_loop(uart, tty, flag, hardware) do defp do_fw_loop(uart, tty, flag, hardware) do
receive do receive do
{:circuits_uart, _, {:error, reason}} -> {: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} -> {:circuits_uart, _, data} ->
if String.contains?(data, "R00") do if String.contains?(data, "R00") do
case flag do case flag do
:idle -> :idle ->
Logger.busy 3, "Waiting for next idle." Farmbot.Logger.busy 3, "Waiting for next idle."
do_fw_loop(uart, tty, :version, hardware) do_fw_loop(uart, tty, :version, hardware)
:version -> :version ->
Process.sleep(500) Process.sleep(500)
# tell the FW to report its version. # tell the FW to report its version.
UART.write(uart, "F83") Nerves.UART.write(uart, "F83")
Logger.busy 3, "Waiting for firmware version report." Farmbot.Logger.busy 3, "Waiting for firmware version report."
do_wait_version(uart, tty, hardware) do_wait_version(uart, tty, hardware)
end end
else else
@ -70,7 +70,7 @@ defmodule Farmbot.Firmware.UartHandler.Update do
end end
after after
15_000 -> 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) do_flash(hardware, uart, tty)
end end
end end
@ -78,7 +78,7 @@ defmodule Farmbot.Firmware.UartHandler.Update do
defp do_wait_version(uart, tty, hardware) do defp do_wait_version(uart, tty, hardware) do
receive do receive do
{:circuits_uart, _, {:error, reason}} -> {: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} -> {:circuits_uart, _, data} ->
case String.split(data, "R83 ") do case String.split(data, "R83 ") do
[_] -> [_] ->
@ -88,7 +88,7 @@ defmodule Farmbot.Firmware.UartHandler.Update do
end end
after after
15_000 -> 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) do_flash(hardware, uart, tty)
end end
end end
@ -98,50 +98,50 @@ defmodule Farmbot.Firmware.UartHandler.Update do
[ver] -> ver [ver] -> ver
[ver, _] -> ver [ver, _] -> ver
end end
expected = Application.get_env(:farmbot, :expected_fw_versions) expected = Application.get_env(:farmbot_core, :expected_fw_versions)
fw_hw = String.last(current_version) fw_hw = String.last(current_version)
cond do cond do
fw_hw != hardware -> fw_hw != hardware ->
Logger.warn 3, "Switching firmware hardware." Farmbot.Logger.warn 3, "Switching firmware hardware."
do_flash(hardware, uart, tty) do_flash(hardware, uart, tty)
current_version in expected -> current_version in expected ->
Logger.success 1, "Firmware is already correct version." Farmbot.Logger.success 1, "Firmware is already correct version."
true -> true ->
Logger.busy 1, "#{current_version} != #{inspect expected}" Farmbot.Logger.busy 1, "#{current_version} != #{inspect expected}"
do_flash(fw_hw, uart, tty) do_flash(fw_hw, uart, tty)
end end
end end
# Farmduino # Farmduino
defp do_flash("F", uart, tty) do 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) Process.sleep(1000)
avrdude("#{:code.priv_dir(:farmbot)}/farmduino.hex", uart, tty) avrdude("#{:code.priv_dir(:farmbot_core)}/farmduino.hex", uart, tty)
end end
defp do_flash("G", uart, tty) do 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) 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 end
# Anything else. (should always be "R") # Anything else. (should always be "R")
defp do_flash(_, uart, tty) do 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) 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 end
defp close(nil) do defp close(nil) do
Logger.info 3, "No uart process." Farmbot.Logger.info 3, "No uart process."
:ok :ok
end end
defp close(uart) do defp close(uart) do
if Process.alive?(uart) do if Process.alive?(uart) do
close = UART.close(uart) close = Nerves.UART.close(uart)
stop = UART.stop(uart) stop = Nerves.UART.stop(uart)
Logger.info 3, "CLOSE: #{inspect close} STOP: #{stop}" Farmbot.Logger.info 3, "CLOSE: #{inspect close} STOP: #{stop}"
Process.sleep(2000) # to allow the FD to be closed. Process.sleep(2000) # to allow the FD to be closed.
end end
end end
@ -149,7 +149,7 @@ defmodule Farmbot.Firmware.UartHandler.Update do
def avrdude(fw_file, uart, tty) do def avrdude(fw_file, uart, tty) do
close(uart) close(uart)
args = ~w"-patmega2560 -cwiring -P#{tty} -b#{@uart_speed} -D -V -Uflash:w:#{fw_file}:i" 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() reset = reset_init()
opts = [stderr_to_stdout: true, into: IO.stream(:stdio, :line)] opts = [stderr_to_stdout: true, into: IO.stream(:stdio, :line)]
do_reset(reset) do_reset(reset)
@ -158,10 +158,10 @@ defmodule Farmbot.Firmware.UartHandler.Update do
Process.sleep(1500) # wait to allow file descriptors to be closed. Process.sleep(1500) # wait to allow file descriptors to be closed.
case res do case res do
{_, 0} -> {_, 0} ->
Logger.success 1, "Firmware flashed! #{fw_file}" Farmbot.Logger.success 1, "Firmware flashed! #{fw_file}"
:ok :ok
{_, err_code} -> {_, 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) Farmbot.Firmware.Utils.replace_firmware_handler(Farmbot.Firmware.StubHandler)
:error :error
end end

View File

@ -30,8 +30,8 @@ defmodule Farmbot.Firmware.Utils do
@doc "replace the firmware handler at runtime." @doc "replace the firmware handler at runtime."
def replace_firmware_handler(handler) do 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) new = Keyword.put(old, :firmware_handler, handler)
Application.put_env(:farmbot, :behaviour, new) Application.put_env(:farmbot_core, :behaviour, new)
end end
end end

View File

@ -1,5 +1,6 @@
defmodule Farmbot.Firmware.Vec3 do defmodule Farmbot.Firmware.Vec3 do
@moduledoc "A three position vector." @moduledoc "A three position vector."
alias Farmbot.Firmware.Vec3
defstruct [x: -1.0, y: -1.0, z: -1.0] defstruct [x: -1.0, y: -1.0, z: -1.0]
@ -8,6 +9,10 @@ defmodule Farmbot.Firmware.Vec3 do
@typedoc @moduledoc @typedoc @moduledoc
@type t :: %__MODULE__{x: number, y: number, z: number} @type t :: %__MODULE__{x: number, y: number, z: number}
def new(x, y, z) do
%Vec3{x: x, y: y, z: z}
end
end end
defimpl Inspect, for: Farmbot.Firmware.Vec3 do defimpl Inspect, for: Farmbot.Firmware.Vec3 do

View File

@ -1,6 +1,6 @@
defmodule Farmbot.Leds do defmodule Farmbot.Leds do
@moduledoc "API for controling Farmbot LEDS." @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!") @led_handler || Mix.raise("You forgot a led handler!")
@valid_status [:off, :solid, :slow_blink, :fast_blink] @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 """ @moduledoc """
Log messages to Farmot endpoints. Log messages to Farmot endpoints.
""" """
use GenStage
def how_many_logs do alias Farmbot.Logger.Repo
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
@doc "Send a debug message to log endpoints" @doc "Send a debug message to log endpoints"
defmacro debug(verbosity, message, meta \\ []) do defmacro debug(verbosity, message, meta \\ []) do
@ -87,30 +54,25 @@ defmodule Farmbot.Logger do
end end
end end
@doc false def insert_log!(%Farmbot.Log{} = log) do
defmacro __using__(_) do Farmbot.Log.changeset(log, %{})
quote do |> Repo.insert!()
alias Farmbot.Logger end
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
]
@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
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 @doc false
def dispatch_log(%Macro.Env{} = env, level, verbosity, message, meta) def dispatch_log(%Macro.Env{} = env, level, verbosity, message, meta)
when level in [:info, :debug, :busy, :warn, :success, :error, :fun] when level in [:info, :debug, :busy, :warn, :success, :error, :fun]
@ -118,71 +80,57 @@ defmodule Farmbot.Logger do
and is_binary(message) and is_binary(message)
and is_list(meta) and is_list(meta)
do 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 = case env.function do
{fun, ar} -> "#{fun}/#{ar}" {fun, ar} -> "#{fun}/#{ar}"
nil -> "no_function" nil -> "no_function"
end end
meta_map = Map.new(meta)
maybe_espeak(message, Map.get(meta_map, :channels, []), state.espeak) struct(Farmbot.Log, [
log = struct(Farmbot.Log, [
time: time,
level: level, level: level,
verbosity: verbosity, verbosity: verbosity,
message: message, message: message,
meta: meta_map, meta: Map.new(meta),
function: fun, function: fun,
file: env.file, file: env.file,
line: env.line, line: env.line,
module: env.module]) 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 = [ logger_meta = [
application: :farmbot, application: :farmbot,
function: fun, function: log.function,
file: env.file, file: log.file,
line: env.line, line: log.line,
module: env.module, module: log.module,
time: time # time: time
] ]
level = log.level
logger_level = if level in [:info, :debug, :warn, :error], do: level, else: :info logger_level = if level in [:info, :debug, :warn, :error], do: level, else: :info
Elixir.Logger.bare_log(logger_level, log, logger_meta) Elixir.Logger.bare_log(logger_level, log, logger_meta)
{:noreply, [log], state} log
end end
def handle_cast({:dispatch_log, %Farmbot.Log{} = log}, state) do @doc "Helper function for deciding if a message should be logged or not."
{:noreply, [log], state} 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 end
defp maybe_espeak(_message, _channels, nil), do: :ok def should_log?(_, _), do: false
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
end 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