Compare commits

...

148 Commits

Author SHA1 Message Date
server 53bc33fc3e Example Debian Buster install script 2020-05-08 15:34:21 -06:00
Rick Carlino 1a7ee04d0b
Merge pull request #1778 from FarmBot/dep_updates
FBJS updates
2020-05-08 16:15:39 -05:00
gabrielburnworth 4a7a683ba7 dep updates (fe) 2020-05-08 13:27:15 -07:00
Rick Carlino 8700d50c81
Merge pull request #1777 from FarmBot/plant_z
Add plant z input
2020-05-07 15:39:35 -05:00
gabrielburnworth 3bab5694b8 add plant z input 2020-05-07 12:15:49 -07:00
Rick Carlino e205214ba4
Merge pull request #1776 from FarmBot/dep_updates
Dependency upgrades
2020-05-07 08:24:47 -05:00
gabrielburnworth 73e9daed05 dep updates (fe) 2020-05-06 15:56:04 -07:00
gabrielburnworth 426f97ddc2 minor step changes 2020-05-06 15:55:59 -07:00
Rick Carlino 88e526cce3
Merge pull request #1775 from FarmBot/point_meta_updates
Merge meta attrs, dont overwrite
2020-05-06 16:40:08 -05:00
Rick Carlino 9e14c2125d
Merge branch 'staging' into point_meta_updates 2020-05-06 15:10:17 -05:00
Rick Carlino 889c78c77a Merge meta attrs, dont overwrite 2020-05-06 15:07:27 -05:00
Rick Carlino 3b1dbe2209
Merge pull request #1774 from FarmBot/mark_as
New Mark As UI
2020-05-01 14:29:18 -05:00
gabrielburnworth 980d39f70d new update_resource ui 2020-05-01 09:00:21 -07:00
Rick Carlino 461f4c2509
Merge pull request #1772 from FarmBot/shutdown_step
Add shutdown sequence step
2020-04-28 10:28:39 -05:00
gabrielburnworth d2176fd6ea dep updates (fe) 2020-04-28 07:20:21 -07:00
gabrielburnworth c9511593a3 add shutdown sequence command 2020-04-28 07:18:04 -07:00
Rick Carlino 66553d143d
Merge pull request #1771 from FarmBot/planted_at_updates
Override `created_at` value with `planted_at` value when available
2020-04-27 21:50:03 -05:00
Rick Carlino 696350343b Override `created_at` value with `planted_at` value when available 2020-04-27 16:09:12 -05:00
Rick Carlino 1f773c44fc
Merge pull request #1770 from FarmBot/planted_at_updates
Set default `planted_at` value
2020-04-27 15:45:50 -05:00
Rick Carlino 87c22d4a96 Set default `planted_at` value 2020-04-27 15:05:53 -05:00
Rick Carlino 9f35dd9992
Merge pull request #1769 from FarmBot/dep_updates
Dependency upgrades
2020-04-24 11:10:19 -05:00
gabrielburnworth 14bf5216e0 dep updates (fe) 2020-04-24 08:22:24 -07:00
gabrielburnworth 1d196d633a Merge branch 'master' of https://github.com/FarmBot/Farmbot-Web-App into staging 2020-04-24 06:49:58 -07:00
Rick Carlino de607e3e3a
Merge pull request #1768 from FarmBot/master-hotfix/update-fallback
Minor hotfix
2020-04-23 20:07:29 -05:00
gabrielburnworth d931cd1b84 update coverage task 2020-04-23 16:55:52 -07:00
gabrielburnworth b3f93dd678 update fallback 2020-04-23 15:58:33 -07:00
Rick Carlino 7f9ecd450d
Merge pull request #1767 from FarmBot/fe_updates
Misc updates
2020-04-23 15:04:05 -05:00
gabrielburnworth 69462e4b60 weeks refactor 2020-04-23 12:12:04 -07:00
gabrielburnworth d3732aed20 version updates 2020-04-23 12:11:25 -07:00
Rick Carlino 6213028f0f
Merge pull request #1765 from FarmBot/mark_as
Mark As step updates
2020-04-21 20:38:10 -05:00
gabrielburnworth 281813369e resource_update -> update_resource (fe) 2020-04-21 14:39:55 -07:00
Rick Carlino 1014eece5f
Merge pull request #1764 from FarmBot/minor-fixes
Minor fixes
2020-04-20 18:06:23 -05:00
gabrielburnworth 6f484ab2e3 minor fixes 2020-04-20 14:07:40 -07:00
Rick Carlino 0bd6d9a967
Merge pull request #1763 from FarmBot/mark_as
Add weed status for Mark As step
2020-04-20 09:26:28 -05:00
gabrielburnworth 25b2f18c4c add weed status for mark as 2020-04-17 16:13:26 -07:00
Rick Carlino 1556084dbd
Merge branch 'staging' into always_upgrade 2020-04-17 13:22:18 -05:00
Rick Carlino 0571100229 Remind custoemrs to upgrade FBOS before troubleshooting 2020-04-17 13:13:32 -05:00
Rick Carlino d6909f439c
Merge pull request #1760 from FarmBot/mark_as
Delete dump_info node
2020-04-17 11:54:09 -05:00
Rick Carlino 36b5c90b65 Merge remote-tracking branch 'origin/mark_as' into mark_as 2020-04-17 11:09:29 -05:00
Rick Carlino f3ac957485 Merge branch 'staging' of github.com:FarmBot/Farmbot-Web-App into mark_as 2020-04-17 11:08:31 -05:00
Rick Carlino 6f834517ca More dump_info removal 2020-04-17 11:08:17 -05:00
Rick Carlino 44c3f7dc4e
Merge branch 'staging' into mark_as 2020-04-17 11:01:53 -05:00
Rick Carlino 5bb77c1c14 Delete dump_info node 2020-04-17 10:58:36 -05:00
Rick Carlino 3ee1478a58
Merge pull request #1759 from FarmBot/mark_as
Plant stage updates.
2020-04-17 10:58:02 -05:00
Rick Carlino df9e0ef26b Update PLANT_STAGES 2020-04-17 09:56:53 -05:00
Rick Carlino 0e02ca06ee Merge branch 'staging' of github.com:FarmBot/Farmbot-Web-App into mark_as 2020-04-16 15:57:29 -05:00
Rick Carlino 643bcb1a37
Merge pull request #1758 from FarmBot/mark_as
Phase 0: Ability to pass variables to MARK AS step
2020-04-16 13:27:52 -05:00
Rick Carlino 88b20a73ea Merge branch 'staging' of github.com:FarmBot/Farmbot-Web-App into mark_as 2020-04-16 13:26:29 -05:00
Rick Carlino e8a8165635
Merge branch 'staging' into mark_as 2020-04-16 13:04:15 -05:00
Rick Carlino 588d4eb36e
Merge pull request #1757 from FarmBot/settings_updates
Settings and dependency updates
2020-04-16 13:04:03 -05:00
Rick Carlino efea80b593 Deprecate `resource_update`. Add `update_resource`. 2020-04-15 13:46:46 -05:00
gabrielburnworth c75d93f3c4 dep updates (fe) 2020-04-14 16:03:48 -07:00
gabrielburnworth bee1e0e074 settings panel updates 2020-04-14 16:03:38 -07:00
Rick Carlino 4375a935f0
Merge pull request #1756 from FarmBot/recovery_release
v9.3.0 - Jolly Juniper
2020-04-14 09:33:24 -05:00
Rick Carlino 7d5fe7c9f6 Deploy fixes 2020-04-14 09:09:07 -05:00
Rick Carlino e801d53d51 Merge branch 'staging' of github.com:FarmBot/Farmbot-Web-App into staging 2020-04-14 08:47:43 -05:00
Rick Carlino bf0a03d11d
Merge pull request #1754 from FarmBot/search_refactor
Search Refactor
2020-04-14 08:46:51 -05:00
gabrielburnworth ec757b1b29 misc bug fixes 2020-04-13 23:38:49 -07:00
gabrielburnworth 3c3b120b9b refactor search fields 2020-04-13 18:15:11 -07:00
Rick Carlino 046035ab9e
Merge pull request #1753 from FarmBot/staging
v9.2.6 - Jolly Juniper
2020-04-13 17:27:06 -05:00
Rick Carlino 52b481e831 Merge branch 'staging' of github.com:FarmBot/Farmbot-Web-App into staging 2020-04-13 16:43:16 -05:00
Rick Carlino 73422eb8ea
Merge pull request #1752 from FarmBot/criteria_ui_updates
Criteria UI updates
2020-04-13 16:42:26 -05:00
gabrielburnworth b087e08f13 criteria ui updates 2020-04-13 12:24:38 -07:00
Rick Carlino 1e1b405c32
Merge pull request #1751 from FarmBot/staging
v9.2.6 release
2020-04-12 11:49:39 -05:00
Rick Carlino dccea4e474
Merge pull request #1750 from FarmBot/image_updates
v9.2.6 - Jolly Juniper
2020-04-12 11:22:26 -05:00
Rick Carlino 91d86bad0c Update structure.sql 2020-04-12 10:55:08 -05:00
Rick Carlino bfe4df68a8 Fix typo in migration 2020-04-12 10:41:53 -05:00
Rick Carlino dd6a43d901 Dep updates 2020-04-12 10:33:49 -05:00
Rick Carlino 726cd6d4e7 Merge branch 'master' into image_updates 2020-04-12 10:24:48 -05:00
Rick Carlino d730cd9260 Bump image limit from 100 to 450 2020-04-12 10:24:17 -05:00
Rick Carlino eb8cfd3c91
Merge pull request #1749 from FarmBot/staging
v9.2.5 - Jolly Juniper
2020-04-08 14:00:45 -05:00
Rick Carlino 8f9bd4a5e7
Merge pull request #1746 from FarmBot/weeds
Add new weed pointer type
2020-04-02 13:51:59 -05:00
gabrielburnworth 3ebf434945 use Weed instead of GenericPointer for weeds 2020-04-02 10:24:33 -07:00
Rick Carlino 92a7194c6e
Merge pull request #1744 from FarmBot/bugfix/add-tool
Fix add tool bug
2020-03-31 09:32:33 -05:00
gabrielburnworth 24ad841d7f add and edit tool improvements 2020-03-30 17:57:32 -07:00
Rick Carlino b45f806309
Merge pull request #1740 from FarmBot/staging
v9.2.4
2020-03-24 12:43:37 -05:00
Rick Carlino cf7ec86106
Merge pull request #1738 from FarmBot/weeds
Part I: Fork "Weed" point type from "GenericPointer"
2020-03-22 20:53:42 -05:00
Rick Carlino 37f7517c51 Temporary changes to pass type checking. May not be correct. 2020-03-22 19:26:19 -05:00
Rick Carlino 2a04803dc6 Fork `Weed` pointer_type from GenericPointer, not Plant 2020-03-22 18:57:47 -05:00
Rick Carlino 3700406687 Add new `Weed` PointerType 2020-03-22 18:32:00 -05:00
Rick Carlino 1ba0ff7871
Merge pull request #1737 from FarmBot/deps
Routine Dep Upgrades
2020-03-22 14:07:55 -05:00
Rick Carlino be23cd44b5 Routine dep upgrades: JS 2020-03-20 11:09:54 -05:00
Rick Carlino 43d1b9da33 Routine dep upgrades: Ruby 2020-03-20 10:31:18 -05:00
Rick Carlino bda30bae09
Merge pull request #1736 from FarmBot/staging
v9.2.3 - Jolly Juniper
2020-03-18 16:23:53 -05:00
Rick Carlino de21cb16da Merge branch 'master' of https://git.heroku.com/farmbot-production into staging 2020-03-18 15:56:33 -05:00
Rick Carlino 0a153bc656 Merge branch 'master' of github.com:FarmBot/Farmbot-Web-App into staging 2020-03-18 15:56:14 -05:00
Rick Carlino 149451c270
Merge pull request #1735 from FarmBot/bot-online-bugfix
Minor bug fix
2020-03-18 15:46:50 -05:00
Gabriel Burnworth d7de315c20
flip bot online boolean 2020-03-18 13:09:11 -07:00
Rick Carlino 61c09b69b9 Merge branch 'master' of github.com:FarmBot/Farmbot-Web-App 2020-03-17 15:29:47 -05:00
Rick Carlino 863071824b
Merge pull request #1733 from FarmBot/staging
v9.2.2 - Jolly Juniper
2020-03-17 15:27:37 -05:00
Rick Carlino 82f2fbef90
Merge pull request #1732 from FarmBot/diagnosis-icons
Diagnosis icons
2020-03-17 14:34:49 -05:00
gabrielburnworth c736348bed add icon to diagnosis indicator 2020-03-17 09:47:17 -07:00
gabrielburnworth 309737dd33 remove plants from default criteria 2020-03-17 08:34:32 -07:00
Rick Carlino 3a9ea9af79
Merge pull request #1731 from FarmBot/staging
v9.2.1 - Jolly Juniper
2020-03-16 19:19:41 -05:00
Rick Carlino bc68f3e79f Merge branch 'staging' 2020-03-16 19:08:55 -05:00
Rick Carlino 85be07efe5
Merge pull request #1730 from FarmBot/panel-updates
Groups panel updates
2020-03-14 14:55:03 -05:00
gabrielburnworth fe9ff346a8 group panel updates 2020-03-13 14:56:14 -07:00
gabrielburnworth ec878e0dae tools panel updates 2020-03-13 14:56:14 -07:00
gabrielburnworth b1c2b36a37 settings refactoring 2020-03-13 14:56:09 -07:00
gabrielburnworth 11f349ac89 dep updates (fe) 2020-03-13 14:06:02 -07:00
Rick Carlino 67373e4f7e
Merge pull request #1726 from FabioDessi/issue-1685
Improve grid line labels in farm designer #2
2020-03-12 12:07:05 -05:00
Fabio Dessi dd46830b9d Merge branch 'staging' into issue-1685 2020-03-03 09:02:11 +01:00
Fabio Dessi 63b333746d [WIP] Grid line improvements for y axis 2020-03-03 08:41:29 +01:00
Rick Carlino 2c6033f57a
Merge pull request #1724 from FarmBot/labeling-improvements
Labeling improvements and refactoring
2020-02-28 13:51:48 -06:00
gabrielburnworth 4d2ea00130 settings updates 2020-02-28 09:01:19 -08:00
gabrielburnworth c8c57d5340 organize German translation file 2020-02-28 08:37:58 -08:00
gabrielburnworth 4013291787 fix commas 2020-02-28 08:35:32 -08:00
gabrielburnworth 25d944e4b7 add missing panels 2020-02-28 08:35:17 -08:00
gabrielburnworth de6f886586 remove unused code 2020-02-28 08:34:54 -08:00
gabrielburnworth 800625e8a1 improve element annotation 2020-02-28 08:34:28 -08:00
Rick Carlino bce0700cd9
Merge pull request #1723 from FarmBot/staging
v9.2.0 - Jolly Juniper
2020-02-27 15:37:03 -06:00
Rick Carlino cccecf58f6
Merge pull request #1722 from MarcRoland/patch-1
Update de.json
2020-02-27 15:14:23 -06:00
MarcRoland c2308cb987
Update de.json 2020-02-27 11:52:14 -08:00
Rick Carlino ffbf660143
Merge pull request #1721 from FarmBot/cleanup
Cleanup
2020-02-26 13:30:15 -08:00
gabrielburnworth edb96d3ca8 cleanup and refactoring 2020-02-26 12:17:54 -08:00
Rick Carlino a49e5e67ba
Merge pull request #1720 from FarmBot/misc-updates
Misc fixes and updates
2020-02-26 11:32:19 -08:00
gabrielburnworth 90ddd78bb8 remove old tools page 2020-02-26 10:28:21 -08:00
gabrielburnworth 9bd98aca1e misc updates 2020-02-26 10:28:16 -08:00
Rick Carlino 49fdced812
Merge pull request #1718 from FarmBot/updates
Dep Upgrades
2020-02-26 06:47:23 -08:00
Rick Carlino 6bc0034d67 Dep updates 2020-02-26 08:18:25 -06:00
Rick Carlino 8e0cf2603c
Merge pull request #1715 from FarmBot/minor-fixes
Minor bug fixes and upgrades
2020-02-24 10:19:51 -08:00
gabrielburnworth 4a0035b9eb minor bug fixes 2020-02-24 08:56:26 -08:00
gabrielburnworth 19eebde8e2 dep updates (fe) 2020-02-24 08:55:37 -08:00
Rick Carlino cd52670c0b
Merge pull request #1711 from FarmBot/days_ago
days => days_ago
2020-02-24 08:52:04 -08:00
Rick Carlino fd62ccd9aa
Merge branch 'staging' into days_ago 2020-02-21 20:17:33 -06:00
Rick Carlino 1a4a106179 days => days_ago 2020-02-21 20:13:29 -06:00
Rick Carlino ee8851b0af
Merge pull request #1709 from FarmBot/tool-updates
Tool updates
2020-02-21 10:32:41 -06:00
gabrielburnworth 40150a307c tool updates 2020-02-20 19:06:43 -08:00
Rick Carlino a5b1d5631e
Merge pull request #1701 from FarmBot/staging
v9.1.3 - Jolly Juniper
2020-02-20 12:57:33 -06:00
Rick Carlino 26a4f66a75
Merge pull request #1700 from FarmBot/tool_deleteion
Fixes for Tool deletion issues
2020-02-19 11:57:17 -06:00
Rick Carlino 73222de627 Dismount tool prior to deletion 2020-02-19 11:15:18 -06:00
Rick Carlino ddb480921e Merge branch 'staging' of github.com:FarmBot/Farmbot-Web-App into tool_deleteion 2020-02-19 11:14:54 -06:00
Rick Carlino 5945d2a38d
Merge pull request #1699 from FarmBot/model_version_updates
Settings and version updates part 2
2020-02-18 16:16:24 -06:00
gabrielburnworth a04ec59ba5 model and version updates part 2 2020-02-18 12:52:52 -08:00
Rick Carlino 310686508f
Merge pull request #1698 from FarmBot/model_updates
Settings and version updates
2020-02-16 08:25:23 -06:00
gabrielburnworth 66b5e3c962 refactor external urls 2020-02-15 11:11:50 -08:00
gabrielburnworth cf0af59e42 model and version updates 2020-02-15 11:08:35 -08:00
gabrielburnworth 9dab0c4bc5 hardware settings ui updates 2020-02-15 11:06:45 -08:00
Rick Carlino 94ee85bd83 Merge branch 'master' of github.com:FarmBot/Farmbot-Web-App 2020-02-14 08:50:32 -06:00
Rick Carlino 672aae441b Merge branch 'staging' of github.com:FarmBot/Farmbot-Web-App 2020-02-14 08:50:26 -06:00
Rick Carlino 3d0223e56a
Merge pull request #1696 from FarmBot/staging
v9.1.1 - Jolly Juniper
2020-02-14 08:48:49 -06:00
Rick Carlino 93d2521511
Merge pull request #1695 from FarmBot/new-models
Add new models
2020-02-14 08:24:51 -06:00
gabrielburnworth aafd84fff7 support new models 2020-02-13 16:53:20 -08:00
Rick Carlino 8f8056a3e3
Merge pull request #1694 from FarmBot/sensor_reading_limits
Cap sensor readings to 5,000 count
2020-02-13 17:07:01 -06:00
Rick Carlino e490aa83f6
Merge pull request #1691 from FarmBot/staging
v9.1.1 - Jolly Juniper
2020-02-10 10:43:19 -06:00
Rick Carlino 3e300defa1
Merge pull request #1682 from FarmBot/staging
v9.1.0 - Jolly Juniper
2020-02-06 07:44:37 -06:00
Rick Carlino 11d08aea99
Merge pull request #1673 from FarmBot/staging
v9.1.0
2020-02-03 08:38:04 -06:00
Rick Carlino c3029d06ed
Merge pull request #1671 from FarmBot/staging
Recovery Deployment, 28 JAN
2020-01-28 13:02:21 -06:00
827 changed files with 18607 additions and 9909 deletions

View File

@ -35,7 +35,7 @@ group :development, :test do
gem "hashdiff"
gem "pry-rails"
gem "pry"
gem "rspec-rails", "4.0.0.beta3"
gem "rspec-rails"
gem "rspec"
gem 'rspec_junit_formatter'
gem "simplecov"

View File

@ -7,38 +7,38 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (6.0.2.1)
actionpack (= 6.0.2.1)
actioncable (6.0.2.2)
actionpack (= 6.0.2.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (6.0.2.1)
actionpack (= 6.0.2.1)
activejob (= 6.0.2.1)
activerecord (= 6.0.2.1)
activestorage (= 6.0.2.1)
activesupport (= 6.0.2.1)
actionmailbox (6.0.2.2)
actionpack (= 6.0.2.2)
activejob (= 6.0.2.2)
activerecord (= 6.0.2.2)
activestorage (= 6.0.2.2)
activesupport (= 6.0.2.2)
mail (>= 2.7.1)
actionmailer (6.0.2.1)
actionpack (= 6.0.2.1)
actionview (= 6.0.2.1)
activejob (= 6.0.2.1)
actionmailer (6.0.2.2)
actionpack (= 6.0.2.2)
actionview (= 6.0.2.2)
activejob (= 6.0.2.2)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (6.0.2.1)
actionview (= 6.0.2.1)
activesupport (= 6.0.2.1)
actionpack (6.0.2.2)
actionview (= 6.0.2.2)
activesupport (= 6.0.2.2)
rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.2.1)
actionpack (= 6.0.2.1)
activerecord (= 6.0.2.1)
activestorage (= 6.0.2.1)
activesupport (= 6.0.2.1)
actiontext (6.0.2.2)
actionpack (= 6.0.2.2)
activerecord (= 6.0.2.2)
activestorage (= 6.0.2.2)
activesupport (= 6.0.2.2)
nokogiri (>= 1.8.5)
actionview (6.0.2.1)
activesupport (= 6.0.2.1)
actionview (6.0.2.2)
activesupport (= 6.0.2.2)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
@ -48,20 +48,20 @@ GEM
activemodel (>= 4.1, < 6.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (6.0.2.1)
activesupport (= 6.0.2.1)
activejob (6.0.2.2)
activesupport (= 6.0.2.2)
globalid (>= 0.3.6)
activemodel (6.0.2.1)
activesupport (= 6.0.2.1)
activerecord (6.0.2.1)
activemodel (= 6.0.2.1)
activesupport (= 6.0.2.1)
activestorage (6.0.2.1)
actionpack (= 6.0.2.1)
activejob (= 6.0.2.1)
activerecord (= 6.0.2.1)
activemodel (6.0.2.2)
activesupport (= 6.0.2.2)
activerecord (6.0.2.2)
activemodel (= 6.0.2.2)
activesupport (= 6.0.2.2)
activestorage (6.0.2.2)
actionpack (= 6.0.2.2)
activejob (= 6.0.2.2)
activerecord (= 6.0.2.2)
marcel (~> 0.3.1)
activesupport (6.0.2.1)
activesupport (6.0.2.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2)
minitest (~> 5.1)
@ -69,11 +69,11 @@ GEM
zeitwerk (~> 2.2)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
amq-protocol (2.3.0)
amq-protocol (2.3.1)
bcrypt (3.1.13)
builder (3.2.4)
bunny (2.14.3)
amq-protocol (~> 2.3, >= 2.3.0)
bunny (2.15.0)
amq-protocol (~> 2.3, >= 2.3.1)
case_transform (0.2)
activesupport
climate_control (0.2.0)
@ -82,9 +82,9 @@ GEM
simplecov
url
coderay (1.1.2)
concurrent-ruby (1.1.5)
concurrent-ruby (1.1.6)
crass (1.0.6)
database_cleaner (1.7.0)
database_cleaner (1.8.3)
declarative (0.0.10)
declarative-option (0.1.0)
delayed_job (4.1.8)
@ -99,27 +99,27 @@ GEM
responders
warden (~> 1.2.3)
diff-lcs (1.3)
digest-crc (0.4.1)
discard (1.1.0)
digest-crc (0.5.1)
discard (1.2.0)
activerecord (>= 4.2, < 7)
docile (1.3.2)
erubi (1.9.0)
factory_bot (5.1.1)
factory_bot (5.1.2)
activesupport (>= 4.2.0)
factory_bot_rails (5.1.1)
factory_bot (~> 5.1.0)
railties (>= 4.2.0)
faker (2.10.1)
faker (2.11.0)
i18n (>= 1.6, < 2)
faraday (0.15.4)
faraday (0.17.3)
multipart-post (>= 1.2, < 3)
faraday_middleware (0.13.1)
faraday_middleware (0.14.0)
faraday (>= 0.7.4, < 1.0)
font-awesome-rails (4.7.0.5)
railties (>= 3.2, < 6.1)
globalid (0.4.2)
activesupport (>= 4.2.0)
google-api-client (0.36.4)
google-api-client (0.37.2)
addressable (~> 2.5, >= 2.5.1)
googleauth (~> 0.9)
httpclient (>= 2.8.1, < 3.0)
@ -127,10 +127,12 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.12)
google-cloud-core (1.4.1)
google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
google-cloud-env (1.3.0)
faraday (~> 0.11)
google-cloud-errors (~> 1.0)
google-cloud-env (1.3.1)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.0.0)
google-cloud-storage (1.25.1)
addressable (~> 2.5)
digest-crc (~> 0.4)
@ -138,14 +140,14 @@ GEM
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
googleauth (0.10.0)
faraday (~> 0.12)
googleauth (0.11.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (~> 0.12)
hashdiff (1.0.0)
hashdiff (1.0.1)
hashie (3.6.0)
httpclient (2.8.3)
i18n (1.8.2)
@ -165,7 +167,7 @@ GEM
activerecord
kaminari-core (= 1.2.0)
kaminari-core (1.2.0)
loofah (2.4.0)
loofah (2.5.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@ -173,56 +175,56 @@ GEM
marcel (0.3.3)
mimemagic (~> 0.3.2)
memoist (0.16.2)
method_source (0.9.2)
mimemagic (0.3.3)
method_source (1.0.0)
mimemagic (0.3.4)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.14.0)
multi_json (1.13.1)
multipart-post (2.1.1)
mutations (0.9.0)
mutations (0.9.1)
activesupport
nio4r (2.5.2)
nokogiri (1.10.7)
nokogiri (1.10.9)
mini_portile2 (~> 2.4.0)
orm_adapter (0.5.0)
os (1.0.1)
passenger (6.0.4)
rack
rake (>= 0.8.1)
pg (1.2.2)
pry (0.12.2)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
pg (1.2.3)
pry (0.13.1)
coderay (~> 1.1)
method_source (~> 1.0)
pry-rails (0.3.9)
pry (>= 0.10.4)
public_suffix (4.0.3)
rabbitmq_http_api_client (1.12.0)
faraday (~> 0.15.4)
faraday_middleware (~> 0.13.0)
rabbitmq_http_api_client (1.13.0)
faraday (>= 0.15, < 1)
faraday_middleware (>= 0.13.0, < 1)
hashie (~> 3.6)
multi_json (~> 1.13.1)
rack (2.1.1)
rack (2.2.2)
rack-attack (6.2.2)
rack (>= 1.0, < 3)
rack-cors (1.1.1)
rack (>= 2.0.0)
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (6.0.2.1)
actioncable (= 6.0.2.1)
actionmailbox (= 6.0.2.1)
actionmailer (= 6.0.2.1)
actionpack (= 6.0.2.1)
actiontext (= 6.0.2.1)
actionview (= 6.0.2.1)
activejob (= 6.0.2.1)
activemodel (= 6.0.2.1)
activerecord (= 6.0.2.1)
activestorage (= 6.0.2.1)
activesupport (= 6.0.2.1)
rails (6.0.2.2)
actioncable (= 6.0.2.2)
actionmailbox (= 6.0.2.2)
actionmailer (= 6.0.2.2)
actionpack (= 6.0.2.2)
actiontext (= 6.0.2.2)
actionview (= 6.0.2.2)
activejob (= 6.0.2.2)
activemodel (= 6.0.2.2)
activerecord (= 6.0.2.2)
activestorage (= 6.0.2.2)
activesupport (= 6.0.2.2)
bundler (>= 1.3.0)
railties (= 6.0.2.1)
railties (= 6.0.2.2)
sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
@ -234,9 +236,9 @@ GEM
rails_stdout_logging
rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5)
railties (6.0.2.1)
actionpack (= 6.0.2.1)
activesupport (= 6.0.2.1)
railties (6.0.2.2)
actionpack (= 6.0.2.2)
activesupport (= 6.0.2.2)
method_source
rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0)
@ -252,44 +254,43 @@ GEM
actionpack (>= 5.0)
railties (>= 5.0)
retriable (3.1.2)
rollbar (2.23.2)
rollbar (2.24.0)
rspec (3.9.0)
rspec-core (~> 3.9.0)
rspec-expectations (~> 3.9.0)
rspec-mocks (~> 3.9.0)
rspec-core (3.9.1)
rspec-support (~> 3.9.1)
rspec-expectations (3.9.0)
rspec-expectations (3.9.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-mocks (3.9.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.9.0)
rspec-rails (4.0.0.beta3)
rspec-rails (4.0.0)
actionpack (>= 4.2)
activesupport (>= 4.2)
railties (>= 4.2)
rspec-core (~> 3.8)
rspec-expectations (~> 3.8)
rspec-mocks (~> 3.8)
rspec-support (~> 3.8)
rspec-core (~> 3.9)
rspec-expectations (~> 3.9)
rspec-mocks (~> 3.9)
rspec-support (~> 3.9)
rspec-support (3.9.2)
rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0)
scenic (1.5.1)
scenic (1.5.2)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
secure_headers (6.3.0)
signet (0.12.0)
signet (0.13.0)
addressable (~> 2.3)
faraday (~> 0.9)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simplecov (0.17.1)
simplecov (0.18.5)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
simplecov-html (~> 0.11)
simplecov-html (0.12.2)
sprockets (4.0.0)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
@ -299,7 +300,7 @@ GEM
sprockets (>= 3.0.0)
thor (1.0.1)
thread_safe (0.3.6)
tzinfo (1.2.6)
tzinfo (1.2.7)
thread_safe (~> 0.1)
uber (0.1.0)
url (0.3.2)
@ -311,7 +312,7 @@ GEM
websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.4)
zeitwerk (2.2.2)
zeitwerk (2.3.0)
PLATFORMS
ruby
@ -347,7 +348,7 @@ DEPENDENCIES
request_store
rollbar
rspec
rspec-rails (= 4.0.0.beta3)
rspec-rails
rspec_junit_formatter
scenic
secure_headers

View File

@ -46,5 +46,6 @@ module Resources
Plant => Points,
Point => Points,
ToolSlot => Points,
Weed => Points,
}
end # Resources

View File

@ -16,9 +16,9 @@ module CeleryScriptSettingsBag
end
PIN_TYPE_MAP = { "Peripheral" => Peripheral,
"Sensor" => Sensor,
"BoxLed3" => BoxLed,
"BoxLed4" => BoxLed }
"Sensor" => Sensor,
"BoxLed3" => BoxLed,
"BoxLed4" => BoxLed }
ALLOWED_AXIS = %w(x y z all)
ALLOWED_ASSERTION_TYPES = %w(abort recover abort_recover continue)
ALLOWED_CHANGES = %w(add remove update)
@ -30,19 +30,19 @@ module CeleryScriptSettingsBag
ALLOWED_PACKAGES = %w(farmbot_os arduino_firmware)
ALLOWED_PIN_MODES = [DIGITAL = 0, ANALOG = 1]
ALLOWED_PIN_TYPES = PIN_TYPE_MAP.keys
ALLOWED_POINTER_TYPE = %w(GenericPointer ToolSlot Plant)
ALLOWED_RESOURCE_TYPE = %w(Device Point Plant ToolSlot GenericPointer)
ALLOWED_POINTER_TYPE = %w(GenericPointer ToolSlot Plant Weed)
ALLOWED_RESOURCE_TYPE = %w(Device Point Plant ToolSlot Weed GenericPointer)
ALLOWED_RPC_NODES = %w(assertion calibrate change_ownership
check_updates dump_info emergency_lock
check_updates emergency_lock
emergency_unlock execute execute_script
factory_reset find_home flash_firmware home
install_farmware install_first_party_farmware _if
move_absolute move_relative power_off read_pin
read_status reboot remove_farmware resource_update
read_status reboot remove_farmware update_resource
send_message set_servo_angle set_user_env sync
take_photo toggle_pin update_farmware wait
write_pin zero)
ALLOWED_SPEC_ACTION = %w(dump_info emergency_lock emergency_unlock power_off
ALLOWED_SPEC_ACTION = %w(emergency_lock emergency_unlock power_off
read_status reboot sync take_photo)
ANY_VARIABLE = %i(tool coordinate point identifier)
BAD_ALLOWED_PIN_MODES = '"%s" is not a valid pin_mode. Allowed values: %s'
@ -73,8 +73,7 @@ module CeleryScriptSettingsBag
ONLY_ONE_COORD = "Move Absolute does not accept a group of locations " \
"as input. Please change your selection to a single" \
" location."
PLANT_STAGES = %w(planned planted harvested sprouted)
RESOURCE_UPDATE_ARGS = [:resource_type, :resource_id, :label, :value]
PLANT_STAGES = %w(planned planted harvested sprouted removed)
SCOPE_DECLARATIONS = [:variable_declaration, :parameter_declaration]
MISC_ENUM_ERR = '"%s" is not valid. Allowed values: %s'
MAX_WAIT_MS = 1000 * 60 * 3 # Three Minutes
@ -82,6 +81,13 @@ module CeleryScriptSettingsBag
"A single wait node cannot exceed #{MAX_WAIT_MS / 1000 / 60} minutes. " +
"Consider lowering the wait time or using multiple WAIT blocks."
Corpus = CeleryScript::Corpus.new
THIS_IS_DEPRECATED = {
args: [:resource_type, :resource_id, :label, :value],
tags: [:function, :api_writer, :network_user],
blk: ->(n) do
n.invalidate!("Deprecated `mark_as` detected. Delete it and re-add")
end,
}
CORPUS_VALUES = {
boolean: [TrueClass, FalseClass],
@ -278,6 +284,9 @@ module CeleryScriptSettingsBag
lua: {
defn: [v(:string)],
},
resource: {
defn: [n(:identifier), n(:resource)],
},
}.map do |(name, conf)|
blk = conf[:blk]
defn = conf.fetch(:defn)
@ -317,10 +326,6 @@ module CeleryScriptSettingsBag
args: [:x, :y, :z],
tags: [:data, :location_like],
},
dump_info: {
tags: [:function, :network_user, :disk_user, :api_writer],
docs: "Sends an info dump to server administrators for troubleshooting.",
},
emergency_lock: {
tags: [:function, :firmware_user, :control_flow],
},
@ -513,15 +518,22 @@ module CeleryScriptSettingsBag
tags: [:function, :firmware_user, :rpi_user],
blk: ->(n) { no_rpi_analog(n) },
},
resource_update: {
args: RESOURCE_UPDATE_ARGS,
tags: [:function, :api_writer, :network_user],
# DEPRECATED- Get rid of this node ASAP -RC 15 APR 2020
resource_update: THIS_IS_DEPRECATED,
resource: {
args: [:resource_type, :resource_id],
tags: [:network_user],
blk: ->(n) do
resource_type = n.args.fetch(:resource_type).value
resource_id = n.args.fetch(:resource_id).value
check_resource_type(n, resource_type, resource_id, Device.current)
end,
},
update_resource: {
args: [:resource],
body: [:pair],
tags: [:function, :api_writer, :network_user],
},
point_group: {
args: [:point_group_id],
tags: [:data, :list_like],
@ -529,7 +541,7 @@ module CeleryScriptSettingsBag
resource_id = n.args.fetch(:point_group_id).value
check_resource_type(n, "PointGroup", resource_id, Device.current)
end,
}
},
}.map { |(name, list)| Corpus.node(name, **list) }
HASH = Corpus.as_json

View File

@ -6,8 +6,9 @@ class InUsePoint < ApplicationRecord
DEFAULT_NAME = "point"
FANCY_NAMES = {
GenericPointer.name => DEFAULT_NAME,
ToolSlot.name => "tool slot",
ToolSlot.name => "slot",
Plant.name => "plant",
Weed.name => "weed"
}
def readonly?

View File

@ -1,20 +1,18 @@
class PinBinding < ApplicationRecord
OFF_LIMITS = [
2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 19, 21, 23, 24, 25, 27
OFF_LIMITS = [
2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 19, 21, 23, 24, 25, 27,
]
BAD_PIN_NUM = \
"The following pin numbers cannot be used: %s" % OFF_LIMITS.join(", ")
BAD_PIN_NUM = "The following pin numbers cannot be used: %s" % OFF_LIMITS.join(", ")
belongs_to :device
belongs_to :sequence
enum special_action: { dump_info: "dump_info",
emergency_lock: "emergency_lock",
enum special_action: { emergency_lock: "emergency_lock",
emergency_unlock: "emergency_unlock",
power_off: "power_off",
read_status: "read_status",
reboot: "reboot",
sync: "sync",
take_photo: "take_photo" }
power_off: "power_off",
read_status: "read_status",
reboot: "reboot",
sync: "sync",
take_photo: "take_photo" }
validates :pin_num, uniqueness: { scope: :device }
def fancy_name

View File

@ -4,7 +4,7 @@ class Point < ApplicationRecord
# axis value > 21k right now - RC
# Using real constants instead of strings results
# in circular dep. errors.
POINTER_KINDS = ["GenericPointer", "Plant", "ToolSlot"]
POINTER_KINDS = ["GenericPointer", "Plant", "ToolSlot", "Weed"]
self.inheritance_column = "pointer_type"
belongs_to :device

View File

@ -4,7 +4,7 @@ class PointGroup < ApplicationRecord
BAD_SORT = "%{value} is not valid. Valid options are: " +
SORT_TYPES.map(&:inspect).join(", ")
DEFAULT_CRITERIA = {
day: { op: "<", days: 0 },
day: { op: "<", days_ago: 0 },
string_eq: {},
number_eq: {},
number_lt: {},

View File

@ -11,7 +11,7 @@ class ToolSlot < Point
MIN_PULLOUT = PULLOUT_DIRECTIONS.min
PULLOUT_ERR = "must be a value between #{MIN_PULLOUT} and #{MAX_PULLOUT}. "\
"%{value} is not valid."
IN_USE = "already in use by another tool slot. "\
IN_USE = "already in use by another slot. "\
"Please un-assign the tool from its current slot"\
" before reassigning."

View File

@ -0,0 +1,2 @@
class Weed < Point
end

View File

@ -7,7 +7,9 @@ module Devices
"genesis_1.2" => Devices::Seeders::GenesisOneTwo,
"genesis_1.3" => Devices::Seeders::GenesisOneThree,
"genesis_1.4" => Devices::Seeders::GenesisOneFour,
"genesis_1.5" => Devices::Seeders::GenesisOneFive,
"genesis_xl_1.4" => Devices::Seeders::GenesisXlOneFour,
"genesis_xl_1.5" => Devices::Seeders::GenesisXlOneFive,
"demo_account" => Devices::Seeders::DemoAccountSeeder,
"none" => Devices::Seeders::None,

View File

@ -27,7 +27,7 @@ module Devices
add_tool_slot(name: ToolNames::SEED_TROUGH_1,
x: 0,
y: 25,
z: -200,
z: 0,
tool: tools_seed_trough_1,
pullout_direction: ToolSlot::NONE,
gantry_mounted: true)
@ -37,25 +37,18 @@ module Devices
add_tool_slot(name: ToolNames::SEED_TROUGH_2,
x: 0,
y: 50,
z: -200,
z: 0,
tool: tools_seed_trough_2,
pullout_direction: ToolSlot::NONE,
gantry_mounted: true)
end
def tool_slots_slot_3
add_tool_slot(name: ToolNames::SEED_TROUGH_3,
x: 0,
y: 75,
z: -200,
tool: tools_seed_trough_3,
pullout_direction: ToolSlot::NONE,
gantry_mounted: true)
end
def tool_slots_slot_3; end
def tool_slots_slot_4; end
def tool_slots_slot_5; end
def tool_slots_slot_6; end
def tool_slots_slot_7; end
def tool_slots_slot_8; end
def tools_seed_bin; end
def tools_seed_tray; end
@ -69,11 +62,6 @@ module Devices
add_tool(ToolNames::SEED_TROUGH_2)
end
def tools_seed_trough_3
@tools_seed_trough_3 ||=
add_tool(ToolNames::SEED_TROUGH_3)
end
def tools_seeder; end
def tools_soil_sensor; end
def tools_watering_nozzle; end

View File

@ -75,6 +75,9 @@ module Devices
tool: tools_weeder)
end
def tool_slots_slot_7; end
def tool_slots_slot_8; end
def tools_seed_bin
@tools_seed_bin ||=
add_tool(ToolNames::SEED_BIN)
@ -87,7 +90,6 @@ module Devices
def tools_seed_trough_1; end
def tools_seed_trough_2; end
def tools_seed_trough_3; end
def tools_seeder
@tools_seeder ||=

View File

@ -37,7 +37,6 @@ module Devices
:tools_seed_tray,
:tools_seed_trough_1,
:tools_seed_trough_2,
:tools_seed_trough_3,
:tools_seeder,
:tools_soil_sensor,
:tools_watering_nozzle,
@ -50,6 +49,8 @@ module Devices
:tool_slots_slot_4,
:tool_slots_slot_5,
:tool_slots_slot_6,
:tool_slots_slot_7,
:tool_slots_slot_8,
# WEBCAM FEEDS ===========================
:webcam_feeds,
@ -152,11 +153,12 @@ module Devices
def tool_slots_slot_4; end
def tool_slots_slot_5; end
def tool_slots_slot_6; end
def tool_slots_slot_7; end
def tool_slots_slot_8; end
def tools_seed_bin; end
def tools_seed_tray; end
def tools_seed_trough_1; end
def tools_seed_trough_2; end
def tools_seed_trough_3; end
def tools_seeder; end
def tools_soil_sensor; end
def tools_watering_nozzle; end

View File

@ -31,7 +31,6 @@ module Devices
LIGHTING = "Lighting"
SEED_TROUGH_1 = "Seed Trough 1"
SEED_TROUGH_2 = "Seed Trough 2"
SEED_TROUGH_3 = "Seed Trough 3"
end
# Stub plants ==============================

View File

@ -0,0 +1,41 @@
module Devices
module Seeders
class GenesisOneFive < AbstractGenesis
def settings_firmware
device
.fbos_config
.update!(firmware_hardware: FbosConfig::FARMDUINO_K15)
end
def tool_slots_slot_7
add_tool_slot(name: ToolNames::SEED_TROUGH_1,
x: 0,
y: 25,
z: 0,
tool: tools_seed_trough_1,
pullout_direction: ToolSlot::NONE,
gantry_mounted: true)
end
def tool_slots_slot_8
add_tool_slot(name: ToolNames::SEED_TROUGH_2,
x: 0,
y: 50,
z: 0,
tool: tools_seed_trough_2,
pullout_direction: ToolSlot::NONE,
gantry_mounted: true)
end
def tools_seed_trough_1
@tools_seed_trough_1 ||=
add_tool(ToolNames::SEED_TROUGH_1)
end
def tools_seed_trough_2
@tools_seed_trough_2 ||=
add_tool(ToolNames::SEED_TROUGH_2)
end
end
end
end

View File

@ -0,0 +1,53 @@
module Devices
module Seeders
class GenesisXlOneFive < AbstractGenesis
def settings_firmware
device
.fbos_config
.update!(firmware_hardware: FbosConfig::FARMDUINO_K15)
end
def settings_device_name
device.update!(name: "FarmBot Genesis XL")
end
def settings_default_map_size_x
device.web_app_config.update!(map_size_x: 5_900)
end
def settings_default_map_size_y
device.web_app_config.update!(map_size_y: 2_900)
end
def tool_slots_slot_7
add_tool_slot(name: ToolNames::SEED_TROUGH_1,
x: 0,
y: 25,
z: 0,
tool: tools_seed_trough_1,
pullout_direction: ToolSlot::NONE,
gantry_mounted: true)
end
def tool_slots_slot_8
add_tool_slot(name: ToolNames::SEED_TROUGH_2,
x: 0,
y: 50,
z: 0,
tool: tools_seed_trough_2,
pullout_direction: ToolSlot::NONE,
gantry_mounted: true)
end
def tools_seed_trough_1
@tools_seed_trough_1 ||=
add_tool(ToolNames::SEED_TROUGH_1)
end
def tools_seed_trough_2
@tools_seed_trough_2 ||=
add_tool(ToolNames::SEED_TROUGH_2)
end
end
end
end

View File

@ -28,11 +28,12 @@ module Devices
def tool_slots_slot_4; end
def tool_slots_slot_5; end
def tool_slots_slot_6; end
def tool_slots_slot_7; end
def tool_slots_slot_8; end
def tools_seed_bin; end
def tools_seed_tray; end
def tools_seed_trough_1; end
def tools_seed_trough_2; end
def tools_seed_trough_3; end
def tools_seeder; end
def tools_soil_sensor; end
def tools_watering_nozzle; end

View File

@ -5,7 +5,7 @@ module PointGroups
hash :criteria do
hash(:day) do
string :op, in: [">", "<"]
integer :days
integer :days_ago
end
hash(:string_eq) { array :*, class: String }
hash(:number_eq) { array :*, class: Integer }

View File

@ -27,11 +27,19 @@ module Points
end
def execute
Point.transaction { point.update!(inputs.except(:point)) && point }
Point.transaction { point.update!(update_params) && point }
end
private
def merged_meta_fields
@merged_meta_fields ||= (point.meta || {}).merge(meta || {})
end
def update_params
@update_params ||= inputs.except(:point).merge(meta: merged_meta_fields)
end
def new_tool_id?
raw_inputs.key?("tool_id")
end

View File

@ -1,9 +1,9 @@
module Tools
class Destroy < Mutations::Command
STILL_IN_USE = "Can't delete tool because the following sequences are "\
"still using it: %s"
STILL_IN_SLOT = "Can't delete tool because it is still in a tool slot. "\
"Please remove it from the tool slot first."
STILL_IN_USE = "Can't delete tool or seed container because the " \
"following sequences are still using it: %s"
STILL_IN_SLOT = "Can't delete tool or seed container because it is " \
"still in a slot. Please remove it from the slot first."
required do
model :tool, class: Tool
@ -15,10 +15,11 @@ module Tools
end
def execute
maybe_unmount_tool
tool.destroy!
end
private
private
def slot
@slot ||= tool.tool_slot
@ -33,8 +34,14 @@ private
end
def names
@names ||= \
@names ||=
InUseTool.where(tool_id: tool.id).pluck(:sequence_name).join(", ")
end
def maybe_unmount_tool
if tool.device.mounted_tool_id == tool.id
tool.device.update!(mounted_tool_id: nil)
end
end
end
end

View File

@ -1,6 +1,24 @@
class BasePointSerializer < ApplicationSerializer
attributes :device_id, :name, :pointer_type, :meta, :x, :y, :z
# PROBLEM:
# * Users need a mutable way to mark a plant's creation time => `planted_at`
# * DB Admin needs to know the _real_ created_at time.
# * We can't change field names (or destroy data) that is in use by legacy devices
#
# SOLUTION:
# * Don't allow users to modify `created_at`
# * Provide `planted_at` if possible.
# * Always provide `planted_at` if it is available
# * Provide a read-only view of `created_at` if `planted_at` is `nil`
def planted_at
object.planted_at || object.created_at
end
def created_at
planted_at
end
def meta
object.meta || {}
end

View File

@ -0,0 +1,3 @@
class WeedSerializer < BasePointSerializer
attributes :radius, :discarded_at, :plant_stage
end

View File

@ -1,5 +1,4 @@
class MakeDefaulDeviceNameFarmbot < ActiveRecord::Migration[5.1]
def change
change_column_default(:devices, :name, "Farmbot")
end

View File

@ -0,0 +1,8 @@
class AddShowWeedsToWebAppConfig < ActiveRecord::Migration[6.0]
def change
add_column :web_app_configs,
:show_weeds,
:boolean,
default: false
end
end

View File

@ -0,0 +1,5 @@
class UpdateMaxImageCount < ActiveRecord::Migration[6.0]
def change
change_column_default(:devices, :max_images_count, 450)
end
end

View File

@ -150,8 +150,8 @@ ALTER SEQUENCE public.alerts_id_seq OWNED BY public.alerts.id;
CREATE TABLE public.ar_internal_metadata (
key character varying NOT NULL,
value character varying,
created_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
created_at timestamp(6) without time zone NOT NULL,
updated_at timestamp(6) without time zone NOT NULL
);
@ -262,7 +262,7 @@ CREATE TABLE public.devices (
id integer NOT NULL,
name character varying DEFAULT 'FarmBot'::character varying,
max_log_count integer DEFAULT 1000,
max_images_count integer DEFAULT 100,
max_images_count integer DEFAULT 450,
timezone character varying(280),
last_saw_api timestamp without time zone,
last_saw_mq timestamp without time zone,
@ -1364,7 +1364,7 @@ CREATE VIEW public.resource_update_steps AS
edge_nodes.kind,
edge_nodes.value
FROM public.edge_nodes
WHERE (((edge_nodes.kind)::text = 'resource_type'::text) AND ((edge_nodes.value)::text = ANY (ARRAY[('"GenericPointer"'::character varying)::text, ('"ToolSlot"'::character varying)::text, ('"Plant"'::character varying)::text])))
WHERE (((edge_nodes.kind)::text = 'resource_type'::text) AND ((edge_nodes.value)::text = ANY ((ARRAY['"GenericPointer"'::character varying, '"ToolSlot"'::character varying, '"Plant"'::character varying])::text[])))
), resource_id AS (
SELECT edge_nodes.primary_node_id,
edge_nodes.kind,
@ -1644,8 +1644,7 @@ CREATE TABLE public.users (
agreed_to_terms_at timestamp without time zone,
confirmation_sent_at timestamp without time zone,
unconfirmed_email character varying,
inactivity_warning_sent_at timestamp without time zone,
inactivity_warning_count integer
inactivity_warning_sent_at timestamp without time zone
);
@ -1729,7 +1728,9 @@ CREATE TABLE public.web_app_configs (
confirm_sequence_deletion boolean DEFAULT true,
discard_unsaved_sequences boolean DEFAULT false,
user_interface_read_only_mode boolean DEFAULT false,
assertion_log integer DEFAULT 1
assertion_log integer DEFAULT 1,
show_zones boolean DEFAULT false,
show_weeds boolean DEFAULT false
);
@ -3376,6 +3377,9 @@ INSERT INTO "schema_migrations" (version) VALUES
('20191219212755'),
('20191220010646'),
('20200116140201'),
('20200204192005');
('20200204192005'),
('20200204230135'),
('20200323235926'),
('20200412152208');

90
debian_example.sh 100644
View File

@ -0,0 +1,90 @@
# How to install FarmBot Web API on a Debian Buster (10) Machine
# IMPORTANT NOTE: Resources are limited and Farmbot, inc. cannot provide
# longterm support to self-hosted users. If you have never administered a
# Ruby on Rails application, we highly advise stopping now. this presents an
# extremely high risk of data loss. Free hosting is provided at
# https://my.farm.bot and eliminates the risks and troubles of self-hosting.
#
# You are highly encouraged to use the my.farm.bot servers. Self hosted
# documentation is provided with the assumption that you have experience with
# Ruby/Javascript development.
#
# Self-hosting a Farmbot server is not a simple task.
# Remove old (possibly broke) docker versions
sudo apt-get remove docker docker-engine docker.io
# Install docker
sudo apt-get install apt-transport-https ca-certificates curl software-properties-common gnupg2 --yes
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian buster stable" --yes
sudo apt-get update --yes
sudo apt-get install docker-ce --yes
sudo docker run hello-world # Should run!
# Install docker-compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.22.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# Install FarmBot Web App
# âš  SKIP THIS STEP IF UPGRADING!
git clone https://github.com/FarmBot/Farmbot-Web-App --depth=5 --branch=master
cd Farmbot-Web-App
#snap install micro --classic # Don't like `micro`? vim, nano, etc are fine, too.
cp example.env .env # âš  SKIP THIS STEP IF UPGRADING!
# == This is a very important step!!! ==
#
# Open `.env` in a text editor and change all the values.
#
# == Nothing will work if you skip this step!!! ==
vim .env # âš  SKIP THIS STEP IF UPGRADING!
# ^ This is the most important step
# READ NOTE ABOVE. Very important!
# Install the correct version of bundler for the project
sudo docker-compose run web gem install bundler:2.1.4
# Install application specific Ruby dependencies
sudo docker-compose run web bundle install
# Install application specific Javascript deps
sudo docker-compose run web npm install
# Create a database in PostgreSQL
sudo docker-compose run web bundle exec rails db:create db:migrate
# Generate a set of *.pem files for data encryption
sudo docker-compose run web rake keys:generate # âš  SKIP THIS STEP IF UPGRADING!
# Build the UI assets via ParcelJS
sudo docker-compose run web rake assets:precompile
# Run the server! ٩(^‿^)۶
# NOTE: DONT TRY TO LOGIN until you see a message similar to this:
# "✨ Built in 44.92s"
# THIS MAY TAKE A VERY LONG TIME ON SLOW MACHINES (~3 minutes on DigitalOcean)
# You will just get an empty screen otherwise.
# This only happens during initialization
sudo docker-compose up
# At this point, setup is complete. Content should be visible at ===============
# http://YOUR_HOST:3000/.
# You can optionally verify installation by running unit tests.
# Create the database for the app to use:
sudo docker-compose run -e RAILS_ENV=test web bundle exec rails db:setup
# Run the tests in the "test" RAILS_ENV:
sudo docker-compose run -e RAILS_ENV=test web rspec spec
# Run user-interface unit tests REQUIRES AT LEAST 4 GB OF RAM:
sudo docker-compose run web npm run test
# === BEGIN OPTIONAL UPGRADES
# To update to later versions of FarmBot,
# shut down the server, create a database backup
# and run commands below.
git pull https://github.com/FarmBot/Farmbot-Web-App.git master
sudo docker-compose build
sudo docker-compose run web bundle install # <== âš  UPGRADE USERS ONLY
sudo docker-compose run web npm install # <== âš  UPGRADE USERS ONLY
sudo docker-compose run web rails db:migrate # <== âš  UPGRADE USERS ONLY
# === END OPTIONAL UPGRADES ^

View File

@ -4,9 +4,15 @@ export const panelState = (): ControlPanelState => {
return {
homing_and_calibration: false,
motors: false,
encoders_and_endstops: false,
encoders: false,
endstops: false,
error_handling: false,
pin_bindings: false,
danger_zone: false,
power_and_reset: false,
pin_guard: false
pin_guard: false,
farm_designer: false,
firmware: false,
farmbot_os: false,
};
};

View File

@ -1,7 +1,8 @@
import { DesignerState } from "../farm_designer/interfaces";
export const fakeDesignerState = (): DesignerState => ({
selectedPlants: undefined,
selectedPoints: undefined,
selectionPointType: undefined,
hoveredPlant: {
plantUUID: undefined,
icon: ""
@ -13,7 +14,10 @@ export const fakeDesignerState = (): DesignerState => ({
cropSearchResults: [],
cropSearchInProgress: false,
chosenLocation: { x: undefined, y: undefined, z: undefined },
currentPoint: undefined,
drawnPoint: undefined,
drawnWeed: undefined,
openedSavedGarden: undefined,
tryGroupSortType: undefined,
editGroupAreaInMap: false,
settingsSearchTerm: "",
});

View File

@ -0,0 +1,2 @@
export const mockDispatch = (innerDispatch = jest.fn()) =>
jest.fn(x => typeof x === "function" && x(innerDispatch));

View File

@ -1,16 +1,10 @@
import { Everything } from "../../interfaces";
import { panelState } from "../control_panel_state";
export const bot: Everything["bot"] = {
"consistent": true,
"stepSize": 100,
"controlPanelState": {
"homing_and_calibration": false,
"motors": false,
"encoders_and_endstops": false,
"danger_zone": false,
"power_and_reset": false,
"pin_guard": false,
},
"controlPanelState": panelState(),
"hardware": {
"gpio_registry": {},
"mcu_params": {

View File

@ -54,5 +54,5 @@ export const fakeImages: TaggedImage[] = [
}
},
"uuid": "Image.7.5"
}
},
];

View File

@ -26,10 +26,11 @@ import {
TaggedAlert,
TaggedPointGroup,
TaggedFolder,
TaggedWeedPointer,
} from "farmbot";
import { fakeResource } from "../fake_resource";
import {
ExecutableType, PinBindingType, Folder
ExecutableType, PinBindingType, Folder,
} from "farmbot/dist/resources/api_resources";
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
import { MessageType } from "../../sequences/interfaces";
@ -133,7 +134,6 @@ export function fakeToolSlot(): TaggedToolSlotPointer {
x: 0,
y: 0,
z: 0,
radius: 25,
pointer_type: "ToolSlot",
meta: {},
tool_id: undefined,
@ -171,6 +171,20 @@ export function fakePoint(): TaggedGenericPointer {
});
}
export function fakeWeed(): TaggedWeedPointer {
return fakeResource("Point", {
id: idCounter++,
name: "Weed 1",
pointer_type: "Weed",
x: 200,
y: 400,
z: 0,
radius: 100,
plant_stage: "planned",
meta: { created_by: "plant-detection", color: "red" }
});
}
export function fakeSavedGarden(): TaggedSavedGarden {
return fakeResource("SavedGarden", {
id: idCounter++,
@ -289,6 +303,7 @@ export function fakeWebAppConfig(): TaggedWebAppConfig {
show_sensor_readings: false,
show_plants: true,
show_points: true,
show_weeds: true,
x_axis_inverted: false,
y_axis_inverted: false,
z_axis_inverted: true,
@ -460,7 +475,7 @@ export function fakePointGroup(): TaggedPointGroup {
sort_type: "xy_ascending",
point_ids: [],
criteria: {
day: { op: "<", days: 0 },
day: { op: "<", days_ago: 0 },
number_eq: {},
number_gt: {},
number_lt: {},

View File

@ -2,7 +2,7 @@ import { Coordinate } from "farmbot";
import { VariableNameSet } from "../resources/interfaces";
export const fakeVariableNameSet = (
label = "parent", vector = { x: 0, y: 0, z: 0 }
label = "parent", vector = { x: 0, y: 0, z: 0 },
): VariableNameSet => {
const data_value: Coordinate = {
kind: "coordinate", args: vector

View File

@ -1,6 +1,6 @@
import moment from "moment";
import {
FarmEventWithExecutable
FarmEventWithExecutable,
} from "../farm_designer/farm_events/calendar/interfaces";
export const TIME = {
@ -24,7 +24,7 @@ export const fakeFarmEventWithExecutable = (): FarmEventWithExecutable => {
color: "red",
name: "faker",
kind: "sequence",
args: { version: 0, locals: { kind: "scope_declaration", args: {} }, }
args: { version: 0, locals: { kind: "scope_declaration", args: {} } }
}
};
};
@ -84,7 +84,7 @@ export const calendarRows = [
"subheading": "25",
"id": 79,
"childExecutableName": "Goto 0, 0, 0 123"
}
},
]
},
{
@ -171,7 +171,7 @@ export const calendarRows = [
"subheading": "25",
"id": 79,
"childExecutableName": "Goto 0, 0, 0 123"
}
},
]
},
{
@ -258,7 +258,7 @@ export const calendarRows = [
"subheading": "25",
"id": 79,
"childExecutableName": "Goto 0, 0, 0 123"
}
},
]
}
},
];

View File

@ -7,4 +7,5 @@ jest.mock("../toast/toast", () => ({
error: jest.fn(),
warning: jest.fn(),
busy: jest.fn(),
removeToast: jest.fn(),
}));

View File

@ -62,7 +62,7 @@ const tr0: TaggedResource = {
},
"speed": 100
}
}
},
],
"args": {
"version": 4,
@ -265,7 +265,6 @@ const tr11: TaggedPoint = {
"pointer_type": "ToolSlot",
"pullout_direction": 0,
"gantry_mounted": false,
"radius": 25,
"x": 10,
"y": 10,
"z": 10,
@ -287,7 +286,7 @@ const tr12: TaggedResource = {
"regimen_id": 11,
"sequence_id": 23,
"time_offset": 345900000
}
},
],
body: [],
},
@ -316,6 +315,28 @@ const tr15: TaggedResource = {
"uuid": "Tool.15.50"
};
const tr16: TaggedPoint = {
specialStatus: SpecialStatus.SAVED,
kind: "Point",
body: {
id: 1395,
created_at: "2017-05-24T20:41:19.889Z",
updated_at: "2017-05-24T20:41:19.889Z",
meta: {
color: "gray",
created_by: "plant-detection"
},
name: "untitled",
pointer_type: "Weed",
plant_stage: "planned",
radius: 10,
x: 490,
y: 421,
z: 5
},
uuid: "Point.1397.11"
};
const log: TaggedLog = {
kind: "Log",
specialStatus: SpecialStatus.SAVED,
@ -345,7 +366,8 @@ export const FAKE_RESOURCES: TaggedResource[] = [
tr0,
tr14,
tr15,
log
tr16,
log,
];
const KIND: keyof TaggedResource = "kind"; // Safety first, kids.
type ResourceGroupNumber = 0 | 1 | 2 | 3 | 4;

View File

@ -9,11 +9,11 @@ import { RawApp as App, AppProps, mapStateToProps } from "../app";
import { mount } from "enzyme";
import { bot } from "../__test_support__/fake_state/bot";
import {
fakeUser, fakeWebAppConfig, fakeFbosConfig, fakeFarmwareEnv
fakeUser, fakeWebAppConfig, fakeFbosConfig, fakeFarmwareEnv,
} from "../__test_support__/fake_state/resources";
import { fakeState } from "../__test_support__/fake_state";
import {
buildResourceIndex
buildResourceIndex,
} from "../__test_support__/resource_index_builder";
import { ResourceName } from "farmbot";
import { fakeTimeSettings } from "../__test_support__/fake_time_settings";
@ -125,7 +125,7 @@ describe("<App />: NavBar", () => {
"Device",
"Sequences",
"Regimens",
"Farmware"
"Farmware",
];
strings.map(string => expect(t).toContain(string));
wrapper.unmount();
@ -157,7 +157,6 @@ describe("mapStateToProps()", () => {
const state = fakeState();
const config = fakeFbosConfig();
config.body.auto_sync = true;
config.body.api_migrated = true;
const fakeEnv = fakeFarmwareEnv();
state.resources = buildResourceIndex([config, fakeEnv]);
state.bot.minOsFeatureData = { api_farmware_env: "8.0.0" };

View File

@ -1,17 +1,16 @@
jest.mock("../util", () => {
return {
attachToRoot: jest.fn(),
// Incidental mock. Can be removed if errors go away.
trim: jest.fn(x => x)
};
});
jest.mock("../util", () => ({
attachToRoot: jest.fn(),
// Incidental mock. Can be removed if errors go away.
trim: jest.fn(x => x),
urlFriendly: jest.fn(),
}));
jest.mock("../redux/store", () => {
return { store: { dispatch: jest.fn() } };
});
jest.mock("../account/dev/dev_support", () => ({
DevSettings: { futureFeaturesEnabled: () => false, }
DevSettings: { futureFeaturesEnabled: () => false }
}));
jest.mock("../config/actions", () => {

View File

@ -0,0 +1,33 @@
jest.unmock("../external_urls");
import { ExternalUrl } from "../external_urls";
/* tslint:disable:max-line-length */
describe("ExternalUrl", () => {
it("returns urls", () => {
expect(ExternalUrl.featureMinVersions)
.toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/staging/FEATURE_MIN_VERSIONS.json");
expect(ExternalUrl.osReleaseNotes)
.toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/staging/RELEASE_NOTES.md");
expect(ExternalUrl.latestRelease)
.toEqual("https://api.github.com/repos/FarmBot/farmbot_os/releases/latest");
expect(ExternalUrl.webAppRepo)
.toEqual("https://github.com/FarmBot/Farmbot-Web-App");
expect(ExternalUrl.gitHubFarmBot)
.toEqual("https://github.com/FarmBot");
expect(ExternalUrl.softwareDocs)
.toEqual("https://software.farm.bot/docs");
expect(ExternalUrl.softwareForum)
.toEqual("https://forum.farmbot.org/c/software");
expect(ExternalUrl.OpenFarm.cropApi)
.toEqual("https://openfarm.cc/api/v1/crops/");
expect(ExternalUrl.OpenFarm.cropBrowse)
.toEqual("https://openfarm.cc/crops/");
expect(ExternalUrl.OpenFarm.newCrop)
.toEqual("https://openfarm.cc/en/crops/new");
expect(ExternalUrl.Video.desktop)
.toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Farm_Designer_Loop.mp4?9552037556691879018");
expect(ExternalUrl.Video.mobile)
.toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Controls.png?9668345515035078097");
});
});

View File

@ -19,7 +19,7 @@ jest.mock("../session", () => ({
}));
import {
responseFulfilled, isLocalRequest, requestFulfilled, responseRejected
responseFulfilled, isLocalRequest, requestFulfilled, responseRejected,
} from "../interceptors";
import { AxiosResponse, Method } from "axios";
import { uuid } from "farmbot";

View File

@ -30,7 +30,6 @@ import "../regimens/editor/interfaces";
import "../regimens/interfaces";
import "../resources/interfaces";
import "../sequences/interfaces";
import "../tools/interfaces";
describe("interfaces", () => {
it("cant explain why coverage is 0 for interface files", () => {

View File

@ -9,7 +9,7 @@ jest.mock("axios", () => ({
}));
jest.mock("../session", () => ({ Session: { clear: jest.fn(), } }));
jest.mock("../session", () => ({ Session: { clear: jest.fn() } }));
import { maybeRefreshToken } from "../refresh_token";
import { API } from "../api/index";

View File

@ -1,6 +1,6 @@
import {
buildResourceIndex,
FAKE_RESOURCES
FAKE_RESOURCES,
} from "../__test_support__/resource_index_builder";
import { TaggedFarmEvent, SpecialStatus } from "farmbot";

View File

@ -12,7 +12,7 @@ type Info = UnboundRouteConfig<{}, {}>;
const fakeCallback = (
component: ConnectedComponent,
child: ConnectedComponent | undefined,
info: Info
info: Info,
) => {
if (info.$ == "*") {
expect(component.name).toEqual("FourOhFour");

View File

@ -11,7 +11,7 @@ jest.mock("axios", () => ({
import { API } from "../../api";
import { Content } from "../../constants";
import {
requestAccountExport, generateFilename
requestAccountExport, generateFilename,
} from "../request_account_export";
import { success } from "../../toast/toast";
import axios from "axios";

View File

@ -3,7 +3,7 @@ import {
Widget,
WidgetHeader,
WidgetBody,
SaveBtn
SaveBtn,
} from "../../ui/index";
import { SpecialStatus } from "farmbot";
import Axios from "axios";

View File

@ -20,7 +20,7 @@ export class DangerousDeleteWidget extends
return <Widget>
<WidgetHeader title={this.props.title} />
<WidgetBody>
<div>
<div className={"dangerous-delete-warning-messages"}>
{t(this.props.warning)}
<br /><br />
{t(this.props.confirmation)}
@ -42,6 +42,7 @@ export class DangerousDeleteWidget extends
<button
onClick={this.onClick}
className="red fb-button"
title={t(this.props.title)}
type="button">
{t(this.props.title)}
</button>

View File

@ -7,7 +7,7 @@ export function ExportAccountPanel(props: { onClick: () => void }) {
return <Widget>
<WidgetHeader title={t("Export Account Data")} />
<WidgetBody>
<div>
<div className={"export-account-data-description"}>
{t(Content.EXPORT_DATA_DESC)}
</div>
<form>
@ -19,6 +19,7 @@ export function ExportAccountPanel(props: { onClick: () => void }) {
</Col>
<Col xs={4}>
<button className="green fb-button" type="button"
title={t("Export")}
onClick={props.onClick}>
{t("Export")}
</button>

View File

@ -1,6 +1,6 @@
import * as React from "react";
import {
BlurableInput, Widget, WidgetHeader, WidgetBody, SaveBtn
BlurableInput, Widget, WidgetHeader, WidgetBody, SaveBtn,
} from "../../ui/index";
import { SettingsPropTypes } from "../interfaces";
import { t } from "../../i18next_wrapper";

View File

@ -8,7 +8,7 @@ import { DevMode } from "../dev_mode";
import * as React from "react";
import { range } from "lodash";
import {
setWebAppConfigValue
setWebAppConfigValue,
} from "../../../config_storage/actions";
import { warning } from "../../../toast/toast";

View File

@ -7,7 +7,7 @@ jest.mock("../../../config_storage/actions", () => ({
import * as React from "react";
import { mount, shallow } from "enzyme";
import {
DevWidget, DevWidgetFERow, DevWidgetFBOSRow, DevWidgetDelModeRow
DevWidget, DevWidgetFERow, DevWidgetFBOSRow, DevWidgetDelModeRow,
} from "../dev_widget";
import { DevSettings } from "../dev_support";
import { setWebAppConfigValue } from "../../../config_storage/actions";

View File

@ -1,6 +1,6 @@
import { store } from "../../redux/store";
import {
getWebAppConfigValue, setWebAppConfigValue
getWebAppConfigValue, setWebAppConfigValue,
} from "../../config_storage/actions";
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";

View File

@ -1,6 +1,6 @@
import * as React from "react";
import {
Widget, WidgetHeader, WidgetBody, Row, Col, BlurableInput
Widget, WidgetHeader, WidgetBody, Row, Col, BlurableInput,
} from "../../ui";
import { ToggleButton } from "../../controls/toggle_button";
import { setWebAppConfigValue } from "../../config_storage/actions";

View File

@ -1,7 +1,7 @@
import * as React from "react";
import { connect } from "react-redux";
import {
Settings, ChangePassword, ExportAccountPanel, DangerousDeleteWidget
Settings, ChangePassword, ExportAccountPanel, DangerousDeleteWidget,
} from "./components";
import { Props } from "./interfaces";
import { Page, Row, Col } from "../ui";
@ -47,12 +47,13 @@ export class RawAccount extends React.Component<Props, State> {
(key: keyof User) => (key === "email") && this.setState({ warnThem: true });
onChange = (e: React.FormEvent<HTMLInputElement>) => {
const { name, value } = e.currentTarget;
if (isKey(name)) {
this.tempHack(name);
this.props.dispatch(edit(this.props.user, { [name]: value }));
const { value } = e.currentTarget;
const field = e.currentTarget.name;
if (isKey(field)) {
this.tempHack(field);
this.props.dispatch(edit(this.props.user, { [field]: value }));
} else {
throw new Error("Bad key: " + name);
throw new Error("Bad key: " + field);
}
};

View File

@ -5,7 +5,7 @@ const mockFeatures = [
storageKey: "weedDetector",
callback: jest.fn(),
value: false
}
},
];
const mocks = {

View File

@ -1,7 +1,7 @@
import { BooleanSetting } from "../../session_keys";
import { Content } from "../../constants";
import {
GetWebAppConfigValue, setWebAppConfigValue
GetWebAppConfigValue, setWebAppConfigValue,
} from "../../config_storage/actions";
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
import { t } from "../../i18next_wrapper";
@ -78,7 +78,7 @@ export const fetchLabFeatures =
storageKey: BooleanSetting.user_interface_read_only_mode,
value: false,
displayInvert: false,
}
},
].map(fetchSettingValue(getConfigValue)));
/** Always allow toggling from true => false (deactivate).

View File

@ -11,7 +11,7 @@ interface LabsFeaturesListProps {
}
export function LabsFeaturesList(props: LabsFeaturesListProps) {
return <div>
return <div className="labs-features-list">
{fetchLabFeatures(props.getConfigValue).map((feature, i) => {
const displayValue = feature.displayInvert ? !feature.value : feature.value;
return <Row key={i}>
@ -23,6 +23,7 @@ export function LabsFeaturesList(props: LabsFeaturesListProps) {
</Col>
<Col xs={2}>
<ToggleButton
title={t("toggle feature")}
toggleValue={displayValue ? 1 : 0}
toggleAction={() => props.onToggle(feature)
.then(() => feature.callback && feature.callback())}

View File

@ -9,9 +9,8 @@ interface DataDumpExport { device?: DeviceAccountSettings; }
type Response = AxiosResponse<DataDumpExport | undefined>;
export function generateFilename({ device }: DataDumpExport): string {
let name: string;
name = device ? (device.name + "_" + device.id) : "farmbot";
return `export_${name}.json`.toLowerCase();
const nameAndId = device ? (device.name + "_" + device.id) : "farmbot";
return `export_${nameAndId}.json`.toLowerCase();
}
// Thanks, @KOL - https://stackoverflow.com/a/19328891/1064917

View File

@ -158,6 +158,10 @@ export class API {
get farmwareInstallationPath() {
return `${this.baseUrl}/api/farmware_installations/`;
}
/** /api/first_party_farmwares */
get firstPartyFarmwarePath() {
return `${this.baseUrl}/api/first_party_farmwares`;
}
/** /api/alerts/:id */
get alertPath() { return `${this.baseUrl}/api/alerts/`; }
/** /api/global_bulletins/:id */

View File

@ -334,6 +334,7 @@ const MUST_CONFIRM_LIST: ResourceName[] = [
"Regimen",
"Image",
"SavedGarden",
"PointGroup",
];
const confirmationChecker = (resourceName: ResourceName, force = false) =>

View File

@ -1,5 +1,6 @@
import * as React from "react";
import { Session } from "./session";
import { ExternalUrl } from "./external_urls";
const OUTER_STYLE: React.CSSProperties = {
borderRadius: "10px",
@ -47,7 +48,7 @@ export function Apology(_: {}) {
<li>
<span>
Send a report to our developer team via the&nbsp;
<a href="http://forum.farmbot.org/c/software">FarmBot software
<a href={ExternalUrl.softwareForum}>FarmBot software
forum</a>. Including additional information (such as steps leading up
to the error) helps us identify solutions more quickly.
</span>

View File

@ -18,7 +18,7 @@ import { validBotLocationData, validFwConfig, validFbosConfig } from "./util";
import { BooleanSetting } from "./session_keys";
import { getPathArray } from "./history";
import {
getWebAppConfigValue, GetWebAppConfigValue
getWebAppConfigValue, GetWebAppConfigValue,
} from "./config_storage/actions";
import { takeSortedLogs } from "./logs/state_to_props";
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
@ -26,11 +26,11 @@ import { getFirmwareConfig, getFbosConfig } from "./resources/getters";
import { intersection } from "lodash";
import { t } from "./i18next_wrapper";
import { ResourceIndex } from "./resources/interfaces";
import { isBotOnline } from "./devices/must_be_online";
import { getStatus } from "./connectivity/reducer_support";
import { isBotOnlineFromState } from "./devices/must_be_online";
import { getAllAlerts } from "./messages/state_to_props";
import { PingDictionary } from "./devices/connectivity/qos";
import { getEnv, getShouldDisplayFn } from "./farmware/state_to_props";
import { filterAlerts } from "./messages/alerts";
/** For the logger module */
init();
@ -81,7 +81,7 @@ export function mapStateToProps(props: Everything): AppProps {
tour: props.resources.consumers.help.currentTour,
resources: props.resources.index,
autoSync: !!(fbosConfig && fbosConfig.auto_sync),
alertCount: getAllAlerts(props.resources).length,
alertCount: getAllAlerts(props.resources).filter(filterAlerts).length,
pings: props.bot.connectivity.pings,
env,
};
@ -99,7 +99,7 @@ const MUST_LOAD: ResourceName[] = [
"FarmEvent",
"Point",
"Device",
"Tool" // Sequence editor needs this for rendering.
"Tool", // Sequence editor needs this for rendering.
];
export class RawApp extends React.Component<AppProps, {}> {
@ -124,8 +124,6 @@ export class RawApp extends React.Component<AppProps, {}> {
const syncLoaded = this.isLoaded;
const currentPage = getPathArray()[2];
const { location_data, mcu_params } = this.props.bot.hardware;
const { sync_status } = this.props.bot.hardware.informational_settings;
const bot2mqtt = this.props.bot.connectivity.uptime["bot.mqtt"];
return <div className="app">
{!syncLoaded && <LoadingPlant animate={this.props.animate} />}
<HotKeys dispatch={this.props.dispatch} />
@ -151,7 +149,7 @@ export class RawApp extends React.Component<AppProps, {}> {
firmwareSettings={this.props.firmwareConfig || mcu_params}
xySwap={this.props.xySwap}
arduinoBusy={!!this.props.bot.hardware.informational_settings.busy}
botOnline={isBotOnline(sync_status, getStatus(bot2mqtt))}
botOnline={isBotOnlineFromState(this.props.bot)}
env={this.props.env}
stepSize={this.props.bot.stepSize} />}
</div>;

View File

@ -3,8 +3,8 @@ jest.mock("axios", () => ({
response: { use: jest.fn() },
request: { use: jest.fn() }
},
post: jest.fn(() => { return Promise.resolve({ data: { foo: "bar" } }); }),
get: jest.fn(() => { return Promise.resolve({ data: { foo: "bar" } }); }),
post: jest.fn(() => Promise.resolve({ data: { foo: "bar" } })),
get: jest.fn(() => Promise.resolve({ data: { foo: "bar" } })),
}));
jest.mock("../../api/api", () => ({
@ -22,6 +22,7 @@ jest.mock("../../devices/actions", () => ({
fetchReleases: jest.fn(),
fetchLatestGHBetaRelease: jest.fn(),
fetchMinOsFeatureData: jest.fn(),
fetchOsReleaseNotes: jest.fn(),
}));
import { didLogin } from "../actions";

View File

@ -1,7 +1,8 @@
import axios from "axios";
import {
fetchReleases, fetchMinOsFeatureData, FEATURE_MIN_VERSIONS_URL,
fetchLatestGHBetaRelease
fetchReleases, fetchMinOsFeatureData,
fetchLatestGHBetaRelease,
fetchOsReleaseNotes,
} from "../devices/actions";
import { AuthState } from "./interfaces";
import { ReduxAction } from "../redux/interfaces";
@ -10,7 +11,7 @@ import { API } from "../api";
import {
responseFulfilled,
responseRejected,
requestFulfilled
requestFulfilled,
} from "../interceptors";
import { Actions } from "../constants";
import { connectDevice } from "../connectivity/connect_device";
@ -24,7 +25,8 @@ export function didLogin(authState: AuthState, dispatch: Function) {
beta_os_update_server && beta_os_update_server != "NOT_SET" &&
dispatch(fetchLatestGHBetaRelease(beta_os_update_server));
dispatch(getFirstPartyFarmwareList());
dispatch(fetchMinOsFeatureData(FEATURE_MIN_VERSIONS_URL));
dispatch(fetchMinOsFeatureData());
dispatch(fetchOsReleaseNotes());
dispatch(setToken(authState));
Sync.fetchSyncData(dispatch);
dispatch(connectDevice(authState));

View File

@ -1,58 +1,72 @@
const mockState = {
auth: {
token: {
unencoded: { iss: "http://geocities.com" }
}
}
};
jest.mock("axios", () => ({
interceptors: {
response: { use: jest.fn() },
request: { use: jest.fn() }
},
get() { return Promise.resolve({ data: mockState }); }
}));
jest.mock("../../session", () => ({
Session: {
fetchStoredToken: jest.fn(),
getAll: () => undefined,
clear: jest.fn()
clear: jest.fn(),
}
}));
jest.mock("../../auth/actions", () => ({
didLogin: jest.fn(),
setToken: jest.fn()
setToken: jest.fn(),
}));
jest.mock("../../refresh_token", () => ({ maybeRefreshToken: jest.fn() }));
let mockTimeout = Promise.resolve({ token: "fake token data" });
jest.mock("promise-timeout", () => ({ timeout: () => mockTimeout }));
import { ready, storeToken } from "../actions";
import { setToken, didLogin } from "../../auth/actions";
import { Session } from "../../session";
import { auth } from "../../__test_support__/fake_state/token";
import { fakeState } from "../../__test_support__/fake_state";
describe("Actions", () => {
it("calls didLogin()", () => {
jest.resetAllMocks();
describe("ready()", () => {
it("uses new token", async () => {
const fakeAuth = { token: "fake token data" };
mockTimeout = Promise.resolve(fakeAuth);
const dispatch = jest.fn();
const thunk = ready();
thunk(dispatch, fakeState);
expect(setToken).toHaveBeenCalled();
const state = fakeState();
console.warn = jest.fn();
await thunk(dispatch, () => state);
expect(setToken).toHaveBeenCalledWith(fakeAuth);
expect(didLogin).toHaveBeenCalledWith(fakeAuth, dispatch);
expect(console.warn).not.toHaveBeenCalled();
expect(Session.clear).not.toHaveBeenCalled();
});
it("Calls Session.clear() when missing auth", () => {
jest.resetAllMocks();
it("uses old token", async () => {
mockTimeout = Promise.reject({ token: "not used" });
const dispatch = jest.fn();
const thunk = ready();
const state = fakeState();
console.warn = jest.fn();
await thunk(dispatch, () => state);
expect(setToken).toHaveBeenLastCalledWith(state.auth);
expect(didLogin).toHaveBeenCalledWith(state.auth, dispatch);
expect(console.warn)
.toHaveBeenCalledWith(expect.stringContaining("Can't refresh token."));
expect(Session.clear).not.toHaveBeenCalled();
});
it("calls Session.clear() when missing auth", () => {
const dispatch = jest.fn();
const state = fakeState();
delete state.auth;
const getState = () => state;
const thunk = ready();
console.warn = jest.fn();
thunk(dispatch, getState);
expect(setToken).not.toHaveBeenCalled();
expect(didLogin).not.toHaveBeenCalled();
expect(console.warn).not.toHaveBeenCalled();
expect(Session.clear).toHaveBeenCalled();
});
});
describe("storeToken()", () => {
it("stores token", () => {
const old = auth;
old.token.unencoded.jti = "old";

View File

@ -1,5 +1,5 @@
import {
toggleWebAppBool, getWebAppConfigValue, setWebAppConfigValue
toggleWebAppBool, getWebAppConfigValue, setWebAppConfigValue,
} from "../actions";
import { BooleanSetting, NumericSetting } from "../../session_keys";
import { edit, save } from "../../api/crud";

View File

@ -4,7 +4,7 @@ import {
BooleanConfigKey,
WebAppConfig,
NumberConfigKey,
StringConfigKey
StringConfigKey,
} from "farmbot/dist/resources/configs/web_app";
import { getWebAppConfig } from "../resources/getters";

View File

@ -10,7 +10,7 @@ import { fakeState } from "../../__test_support__/fake_state";
import { GetState } from "../../redux/interfaces";
import { handleInbound } from "../auto_sync_handle_inbound";
import {
handleCreateOrUpdate
handleCreateOrUpdate,
} from "../auto_sync";
import { destroyOK } from "../../resources/actions";
import { SkipMqttData, BadMqttData, UpdateMqttData, DeleteMqttData } from "../interfaces";

View File

@ -4,7 +4,7 @@ import {
asTaggedResource,
handleCreate,
handleUpdate,
handleCreateOrUpdate
handleCreateOrUpdate,
} from "../auto_sync";
import { SpecialStatus, TaggedSequence } from "farmbot";
import { Actions } from "../../constants";

View File

@ -35,7 +35,7 @@ describe("attachEventListeners", () => {
].map(e => expect(dev.on).toHaveBeenCalledWith(e, expect.any(Function)));
[
"message",
"reconnect"
"reconnect",
].map(e => {
if (dev.client) {
expect(dev.client.on).toHaveBeenCalledWith(e, expect.any(Function));

View File

@ -37,7 +37,9 @@ import { getDevice } from "../../../device";
import { talk } from "browser-speech";
import { MessageType } from "../../../sequences/interfaces";
import { FbjsEventName } from "farmbot/dist/constants";
import { info, error, success, warning, fun, busy } from "../../../toast/toast";
import {
info, error, success, warning, fun, busy, removeToast,
} from "../../../toast/toast";
import { onLogs } from "../../log_handlers";
import { fakeState } from "../../../__test_support__/fake_state";
import { globalQueue } from "../../batch_queue";
@ -177,7 +179,8 @@ describe("onOffline", () => {
jest.resetAllMocks();
onOffline();
expect(dispatchNetworkDown).toHaveBeenCalledWith("user.mqtt", ANY_NUMBER);
expect(error).toHaveBeenCalledWith(Content.MQTT_DISCONNECTED);
expect(error).toHaveBeenCalledWith(
Content.MQTT_DISCONNECTED, "Error", "red", "offline");
});
});
@ -186,13 +189,17 @@ describe("onOnline", () => {
jest.resetAllMocks();
onOnline();
expect(dispatchNetworkUp).toHaveBeenCalledWith("user.mqtt", ANY_NUMBER);
expect(removeToast).toHaveBeenCalledWith("offline");
});
});
describe("onReconnect", () => {
onReconnect();
expect(warning).toHaveBeenCalledWith(
"Attempting to reconnect to the message broker", "Offline", "yellow");
describe("onReconnect()", () => {
it("sends reconnect toast", () => {
onReconnect();
expect(warning).toHaveBeenCalledWith(
"Attempting to reconnect to the message broker",
"Offline", "yellow", "offline");
});
});
describe("changeLastClientConnected", () => {
@ -268,7 +275,8 @@ describe("onPublicBroadcast", () => {
console.log = jest.fn();
onPublicBroadcast({});
expectBroadcastLog();
expect(window.alert).toHaveBeenCalledWith(Content.FORCE_REFRESH_CANCEL_WARNING);
expect(window.alert).toHaveBeenCalledWith(
Content.FORCE_REFRESH_CANCEL_WARNING);
expect(location.assign).not.toHaveBeenCalled();
});
});

View File

@ -1,21 +1,15 @@
jest.mock("../../slow_down", () => {
return {
slowDown: jest.fn((fn: Function) => fn),
};
});
jest.mock("../../../devices/actions", () => ({
badVersion: jest.fn(),
EXPECTED_MAJOR: 1,
EXPECTED_MINOR: 0,
jest.mock("../../slow_down", () => ({
slowDown: jest.fn((fn: Function) => fn)
}));
jest.mock("../../../devices/actions", () => ({ badVersion: jest.fn() }));
import {
onStatus,
incomingStatus,
incomingLegacyStatus,
onLegacyStatus,
HACKY_FLAGS
HACKY_FLAGS,
} from "../../connect_device";
import { slowDown } from "../../slow_down";
import { fakeState } from "../../../__test_support__/fake_state";
@ -49,8 +43,10 @@ describe("onStatus()", () => {
});
it("version ok", () => {
globalConfig.MINIMUM_FBOS_VERSION = "1.0.0";
callOnStatus("1.0.0");
expect(badVersion).not.toHaveBeenCalled();
delete globalConfig.MINIMUM_FBOS_VERSION;
});
});

View File

@ -20,7 +20,7 @@ import { getDevice } from "../../device";
import { store } from "../../redux/store";
import { Actions } from "../../constants";
import {
startTracking, outstandingRequests, stopTracking, cleanUUID
startTracking, outstandingRequests, stopTracking, cleanUUID,
} from "../data_consistency";
const unprocessedUuid = "~UU.ID~";

View File

@ -8,7 +8,7 @@ jest.mock("../index", () => ({
import {
readPing,
startPinging,
PING_INTERVAL
PING_INTERVAL,
} from "../ping_mqtt";
import { Farmbot, RpcRequest, RpcRequestBodyItem } from "farmbot";
import { FarmBotInternalConfig } from "farmbot/dist/config";

View File

@ -41,7 +41,7 @@ describe("connectivity reducer", () => {
it("broadcasts PING_OK", () => {
pingOK("yep", 123);
expect(store.dispatch).toHaveBeenCalledWith({
payload: { at: 123, id: "yep", },
payload: { at: 123, id: "yep" },
type: "PING_OK",
});
});

View File

@ -4,7 +4,7 @@ import { TaggedResource, SpecialStatus } from "farmbot";
import { overwrite, init } from "../api/crud";
import { handleInbound } from "./auto_sync_handle_inbound";
import {
SyncPayload, MqttDataResult, Reason, UpdateMqttData
SyncPayload, MqttDataResult, Reason, UpdateMqttData,
} from "./interfaces";
import { outstandingRequests } from "./data_consistency";
import { newTaggedResource } from "../sync/actions";

View File

@ -4,17 +4,13 @@ import { Log } from "farmbot/dist/resources/api_resources";
import { Farmbot, BotStateTree, TaggedResource } from "farmbot";
import { FbjsEventName } from "farmbot/dist/constants";
import { noop } from "lodash";
import { success, error, info, warning, fun, busy } from "../toast/toast";
import {
success, error, info, warning, fun, busy, removeToast,
} from "../toast/toast";
import { HardwareState } from "../devices/interfaces";
import { GetState, ReduxAction } from "../redux/interfaces";
import { Content, Actions } from "../constants";
import {
EXPECTED_MAJOR,
EXPECTED_MINOR,
commandOK,
badVersion,
commandErr
} from "../devices/actions";
import { commandOK, badVersion, commandErr } from "../devices/actions";
import { init } from "../api/crud";
import { AuthState } from "../auth/interfaces";
import { autoSync } from "./auto_sync";
@ -108,11 +104,6 @@ export function readStatus() {
.then(() => { commandOK(noun); }, commandErr(noun));
}
export const onOffline = () => {
dispatchNetworkDown("user.mqtt", now());
error(t(Content.MQTT_DISCONNECTED));
};
export const changeLastClientConnected = (bot: Farmbot) => () => {
bot.setUserEnv({
"LAST_CLIENT_CONNECTED": JSON.stringify(new Date())
@ -123,7 +114,7 @@ const setBothUp = () => bothUp();
const legacyChecks = (getState: GetState) => {
const { controller_version } = getState().bot.hardware.informational_settings;
if (HACKY_FLAGS.needVersionCheck && controller_version) {
const IS_OK = versionOK(controller_version, EXPECTED_MAJOR, EXPECTED_MINOR);
const IS_OK = versionOK(controller_version);
if (!IS_OK) { badVersion(); }
HACKY_FLAGS.needVersionCheck = false;
}
@ -163,14 +154,20 @@ export function onMalformed() {
}
}
export const onOnline =
() => {
success(t("Reconnected to the message broker."), t("Online"));
dispatchNetworkUp("user.mqtt", now());
};
export const onReconnect =
() => warning(t("Attempting to reconnect to the message broker"),
t("Offline"), "yellow");
export const onOnline = () => {
removeToast("offline");
success(t("Reconnected to the message broker."), t("Online"));
dispatchNetworkUp("user.mqtt", now());
};
export const onReconnect = () =>
warning(t("Attempting to reconnect to the message broker"),
t("Offline"), "yellow", "offline");
export const onOffline = () => {
dispatchNetworkDown("user.mqtt", now());
error(t(Content.MQTT_DISCONNECTED), t("Error"), "red", "offline");
};
export function onPublicBroadcast(payl: unknown) {
console.log(FbjsEventName.publicBroadcast, payl);

View File

@ -3,7 +3,7 @@ import {
actOnChannelName,
showLogOnScreen,
speakLogAloud,
initLog
initLog,
} from "./connect_device";
import { GetState } from "../redux/interfaces";
import { Log } from "farmbot/dist/resources/api_resources";

View File

@ -4,7 +4,7 @@ import {
dispatchNetworkUp,
dispatchQosStart,
pingOK,
pingNO
pingNO,
} from "./index";
import { isNumber } from "lodash";
import axios from "axios";

View File

@ -2,7 +2,7 @@ import { generateReducer } from "../redux/generate_reducer";
import { Actions } from "../constants";
import {
ConnectionState,
EdgeStatus
EdgeStatus,
} from "./interfaces";
import { startPing, completePing, failPing } from "../devices/connectivity/qos";

View File

@ -1,5 +1,5 @@
import { ConnectionStatus } from "./interfaces";
export function getStatus(cs: ConnectionStatus | undefined): "up" | "down" {
return (cs && cs.state) || "down";
return cs?.state || "down";
}

View File

@ -2,6 +2,27 @@ import { trim } from "./util";
export namespace ToolTips {
// Farm Designer: Groups
export const SORT_DESCRIPTION =
trim(`When executing a sequence over a Group of locations, FarmBot will
travel to each group member in the order of the chosen sort method.
If the random option is chosen, FarmBot will travel in a random order
every time, so the ordering shown below will only be representative.`);
export const CRITERIA_SELECTION_COUNT =
trim(`Filter additions can only be removed by changing filters.
Click and drag in the map to modify selection filters.
Filters will be applied at the time of sequence execution. The final
selection at that time may differ from the selection currently
displayed.`);
export const CRITERIA_ALPHA_FEATURE =
trim(`Group filters is a new feature under active development.
Use with caution.`);
export const DOT_NOTATION_TIP =
trim(`Tip: Use dot notation (i.e., 'meta.color') to access meta fields.`);
// Controls
export const MOVE =
trim(`Use these manual control buttons to move FarmBot in realtime.
@ -12,7 +33,12 @@ export namespace ToolTips {
export const WEBCAM =
trim(`If you have a webcam, you can view the video stream in this widget.
Press the edit button to update and save your webcam URL.`);
Press the edit button to update and save your webcam URL.
Note: Some webcam services do not allow webcam feeds to be embedded in
other sites. If you see a web browser error after adding a webcam feed,
there is unfortunately nothing FarmBot can do to fix the problem.
Please contact your webcam's customer support to see if the security
policy for embedding feeds into other sites can be changed.`);
export const PERIPHERALS =
trim(`Use these toggle switches to control FarmBot's peripherals in
@ -26,10 +52,19 @@ export namespace ToolTips {
export const SENSOR_HISTORY =
trim(`View and filter historical sensor reading data.`);
// Device
export const OS_SETTINGS =
trim(`View and change device settings.`);
// FarmBot OS Settings: Firmware
export const FIRMWARE_VALUE_API =
trim(`Firmware value from your choice in the dropdown to the left, as
understood by the Web App.`);
export const FIRMWARE_VALUE_FBOS =
trim(`Firmware value reported from the firmware, as understood by
FarmBot OS.`);
export const FIRMWARE_VALUE_MCU =
trim(`Firmware value reported from the firmware.`);
// Hardware Settings
export const HW_SETTINGS =
trim(`Change settings of your FarmBot hardware with the fields below.
Caution: Changing these settings to extreme values can cause hardware
@ -38,37 +73,39 @@ export namespace ToolTips {
Tip: Recalibrate FarmBot after changing settings and test a
few sequences to verify that everything works as expected.`);
export const PIN_BINDINGS =
trim(`Assign a sequence to execute when a Raspberry Pi GPIO pin is
activated.`);
export const PIN_BINDING_WARNING =
trim(`Warning: Binding to a pin without a physical button and
pull-down resistor connected may put FarmBot into an unstable state.`);
// Connectivity
export const CONNECTIVITY =
trim(`Diagnose connectivity issues with FarmBot and the browser.`);
// Hardware Settings: Homing and Calibration
export const HOMING =
export const HOMING_ENCODERS =
trim(`If encoders or end-stops are enabled, home axis (find zero).`);
export const CALIBRATION =
export const HOMING_STALL_DETECTION =
trim(`If stall detection or end-stops are enabled, home axis
(find zero).`);
export const CALIBRATION_ENCODERS =
trim(`If encoders or end-stops are enabled, home axis and determine
maximum.`);
export const CALIBRATION_STALL_DETECTION =
trim(`If stall detection or end-stops are enabled, home axis and
determine maximum.`);
export const SET_ZERO_POSITION =
trim(`Set the current location as zero.`);
export const FIND_HOME_ON_BOOT =
export const FIND_HOME_ON_BOOT_ENCODERS =
trim(`If encoders or end-stops are enabled, find the home position
when the device powers on.
Warning! This will perform homing on all axes when the
device powers on. Encoders or endstops must be enabled.
when the device powers on. Warning! This will perform homing on all
axes when the device powers on. Encoders or endstops must be enabled.
It is recommended to make sure homing works properly before enabling
this feature. (default: disabled)`);
export const FIND_HOME_ON_BOOT_STALL_DETECTION =
trim(`If stall detection or end-stops are enabled, find the home
position when the device powers on. Warning! This will perform homing
on all axes when the device powers on. Stall detection or endstops
must be enabled. It is recommended to make sure homing works properly
before enabling this feature. (default: disabled)`);
export const STOP_AT_HOME =
trim(`Stop at the home location of the axis. (default: disabled)`);
@ -85,18 +122,7 @@ export namespace ToolTips {
trim(`Set the length of each axis to provide software limits.
Used only if STOP AT MAX is enabled. (default: 0 (disabled))`);
export const TIMEOUT_AFTER =
trim(`Amount of time to wait for a command to execute before stopping.
(default: 120s)`);
// Hardware Settings: Motors
export const MAX_MOVEMENT_RETRIES =
trim(`Number of times to retry a movement before stopping. (default: 3)`);
export const E_STOP_ON_MOV_ERR =
trim(`Emergency stop if movement is not complete after the maximum
number of retries. (default: disabled)`);
export const MAX_SPEED =
trim(`Maximum travel speed after acceleration in millimeters per second.
(default: x: 80mm/s, y: 80mm/s, z: 16mm/s)`);
@ -132,18 +158,22 @@ export namespace ToolTips {
export const MOTOR_CURRENT =
trim(`Motor current in milliamps. (default: 600)`);
export const STALL_SENSITIVITY =
trim(`Motor stall sensitivity. (default: 30)`);
export const ENABLE_X2_MOTOR =
trim(`Enable use of a second x-axis motor. Connects to E0 on RAMPS.
(default: enabled)`);
// Hardware Settings: Encoders and Endstops
// Hardware Settings: Encoders / Stall Detection
export const ENABLE_ENCODERS =
trim(`Enable use of rotary encoders for stall detection,
calibration and homing. (default: enabled)`);
export const ENABLE_STALL_DETECTION =
trim(`Enable use of motor stall detection for detecting missed steps,
calibration and homing. (default: enabled)`);
export const STALL_SENSITIVITY =
trim(`Motor stall sensitivity. (default: 30)`);
export const ENCODER_POSITIONING =
trim(`Use encoders for positioning. (default: disabled)`);
@ -151,17 +181,22 @@ export namespace ToolTips {
trim(`Reverse the direction of encoder position reading.
(default: disabled)`);
export const MAX_MISSED_STEPS =
export const MAX_MISSED_STEPS_ENCODERS =
trim(`Number of steps missed (determined by encoder) before motor is
considered to have stalled. (default: 5)`);
export const ENCODER_MISSED_STEP_DECAY =
export const MAX_MISSED_STEPS_STALL_DETECTION =
trim(`Number of steps missed (determined by motor stall detection) before
motor is considered to have stalled. (default: 5)`);
export const MISSED_STEP_DECAY =
trim(`Reduction to missed step total for every good step. (default: 5)`);
export const ENCODER_SCALING =
trim(`encoder scaling factor = 10000 * (motor resolution * microsteps)
/ (encoder resolution). (default: 5556 (10000*200/360))`);
// Hardware Settings: Endstops
export const ENABLE_ENDSTOPS =
trim(`Enable use of electronic end-stops for end detection,
calibration and homing. (default: disabled)`);
@ -173,18 +208,33 @@ export namespace ToolTips {
trim(`Invert axis end-stops. Enable for normally closed (NC),
disable for normally open (NO). (default: disabled)`);
// Hardware Settings: Error Handling
export const TIMEOUT_AFTER =
trim(`Amount of time to wait for a command to execute before stopping.
(default: 120s)`);
export const MAX_MOVEMENT_RETRIES =
trim(`Number of times to retry a movement before stopping. (default: 3)`);
export const E_STOP_ON_MOV_ERR =
trim(`Emergency stop if movement is not complete after the maximum
number of retries. (default: disabled)`);
// Hardware Settings: Pin Guard
export const PIN_GUARD_PIN_NUMBER =
trim(`The number of the pin to guard. This pin will be set to the specified
state after the duration specified by TIMEOUT.`);
// Hardware Settings: Pin Bindings
export const PIN_BINDINGS =
trim(`Assign an action or sequence to execute when a Raspberry Pi
GPIO pin is activated.`);
export const PIN_BINDING_WARNING =
trim(`Warning: Binding to a pin without a physical button and
pull-down resistor connected may put FarmBot into an unstable state.`);
// Farmware
export const FARMWARE =
trim(`Manage Farmware (plugins).`);
export const FARMWARE_LIST =
trim(`View, select, and install new Farmware.`);
export const PHOTOS =
trim(`Take and view photos with your FarmBot's camera.`);
@ -208,9 +258,6 @@ export namespace ToolTips {
You can also edit, copy, and delete existing sequences;
assign a color; and give your commands custom names.`);
export const SEQUENCE_LIST =
trim(`Here is the list of all of your sequences. Click one to edit.`);
export const DEFAULT_VALUE =
trim(`Select a location to be used as the default value for this variable.
If the sequence is ever run without the variable explicitly set to
@ -263,8 +310,12 @@ export namespace ToolTips {
export const FIND_HOME =
trim(`The Find Home step instructs the device to perform a homing
command (using encoders or endstops) to find and set zero for
the chosen axis or axes.`);
command (using encoders, stall detection, or endstops) to find and set
zero for the chosen axis or axes.`);
export const CALIBRATE =
trim(`If encoders, stall detection, or end-stops are enabled,
home axis and determine maximum.`);
export const IF =
trim(`Execute a sequence if a condition is satisfied. If the condition
@ -284,6 +335,7 @@ export namespace ToolTips {
export const TAKE_PHOTO =
trim(`Snaps a photo using the device camera. Select the camera type
on the Device page.`);
export const EMERGENCY_LOCK =
trim(`Stops a device from moving until it is unlocked by a user.`);
@ -294,10 +346,7 @@ export namespace ToolTips {
trim(`The Mark As step allows FarmBot to programmatically edit the
properties of the UTM, plants, and weeds from within a sequence.
For example, you can mark a plant as "planted" during a seeding
sequence or delete a weed after removing it.`);
export const REBOOT =
trim(`Power cycle FarmBot's onboard computer.`);
sequence or mark a weed as "removed" after removing it.`);
export const SET_SERVO_ANGLE =
trim(`Move a servo to the provided angle. An angle of 90 degrees
@ -310,6 +359,9 @@ export namespace ToolTips {
export const MOVE_TO_HOME =
trim(`Move FarmBot to home for the provided axis.`);
export const ASSERTION =
trim(`Evaluate Lua commands. For power users and software developers.`);
export const FIRMWARE_ACTION =
trim(`FarmBot OS or micro-controller firmware action.`);
@ -335,20 +387,6 @@ export namespace ToolTips {
growing at the same or different times. Multiple regimens can be
applied to any one plant.`);
export const REGIMEN_LIST =
trim(`This is a list of all of your regimens. Click one to begin
editing it.`);
// Tools
export const TOOL_LIST =
trim(`This is a list of all your FarmBot tools and seed containers.
Click the Edit button to add, edit, or delete tools or seed containers.`);
export const TOOLBAY_LIST =
trim(`Tool slots are where you store your FarmBot tools and seed
containers, which should be reflective of your real FarmBot hardware
configuration.`);
// Logs
export const LOGS =
trim(`View and filter log messages.`);
@ -371,16 +409,6 @@ export namespace ToolTips {
export const FIRMWARE_DEBUG_MESSAGES =
trim(`Log all debug received from firmware (clears after refresh).`);
export const MESSAGES =
trim(`View messages.`);
// App
export const LABS =
trim(`Customize your web app experience.`);
export const TOURS =
trim(`Take a guided tour of the Web App.`);
}
export namespace Content {
@ -484,11 +512,9 @@ export namespace Content {
real account at`);
// App Settings
export const CONFIRM_STEP_DELETION =
trim(`Show a confirmation dialog when deleting a sequence step.`);
export const CONFIRM_SEQUENCE_DELETION =
trim(`Show a confirmation dialog when deleting a sequence.`);
export const TIME_FORMAT_24_HOUR =
trim(`Display time using the 24-hour notation,
i.e., 23:00 instead of 11:00pm`);
export const HIDE_WEBCAM_WIDGET =
trim(`If not using a webcam, use this setting to remove the
@ -498,14 +524,6 @@ export namespace Content {
trim(`If not using sensors, use this setting to remove the
widget from the Controls page.`);
export const DYNAMIC_MAP_SIZE =
trim(`Change the garden map size based on axis length.
A value must be input in AXIS LENGTH and STOP AT MAX must be enabled in
the HARDWARE widget. Overrides MAP SIZE values.`);
export const PLANT_ANIMATIONS =
trim(`Enable plant animations in the garden map.`);
export const BROWSER_SPEAK_LOGS =
trim(`Have the browser also read aloud log messages on the
"Speak" channel that are spoken by FarmBot.`);
@ -518,22 +536,25 @@ export namespace Content {
trim(`Warning! When enabled, any unsaved changes
will be discarded when refreshing or closing the page. Are you sure?`);
export const DISCARD_UNSAVED_SEQUENCE_CHANGES =
trim(`Don't ask about saving sequence work before
closing browser tab. Warning: may cause loss of data.`);
export const EMERGENCY_UNLOCK_CONFIRM_CONFIG =
trim(`Confirm when unlocking FarmBot after an emergency stop.`);
export const DISCARD_UNSAVED_SEQUENCE_CHANGES_CONFIRM =
trim(`Warning! When enabled, any unsaved changes to sequences
will be discarded when refreshing or closing the page. Are you sure?`);
export const CONFIRM_EMERGENCY_UNLOCK_CONFIRM_DISABLE =
trim(`Warning! When disabled, clicking the UNLOCK button will immediately
unlock FarmBot instead of confirming that it is safe to do so.
As a result, double-clicking the E-STOP button may not stop FarmBot.
Are you sure you want to disable this feature?`);
export const VIRTUAL_TRAIL =
trim(`Display a virtual trail for FarmBot in the garden map to show
movement and watering history while the map is open. Toggling this setting
will clear data for the current trail.`);
export const USER_INTERFACE_READ_ONLY_MODE =
trim(`Disallow account data changes. This does
not prevent Farmwares or FarmBot OS from changing settings.`);
export const TIME_FORMAT_24_HOUR =
trim(`Display time using the 24-hour notation,
i.e., 23:00 instead of 11:00pm`);
// Sequence Settings
export const CONFIRM_STEP_DELETION =
trim(`Show a confirmation dialog when deleting a sequence step.`);
export const CONFIRM_SEQUENCE_DELETION =
trim(`Show a confirmation dialog when deleting a sequence.`);
export const SHOW_PINS =
trim(`Show raw pin lists in Read Sensor, Control Peripheral, and
@ -542,18 +563,27 @@ export namespace Content {
export const EXPAND_STEP_OPTIONS =
trim(`Choose whether advanced step options are open or closed by default.`);
export const EMERGENCY_UNLOCK_CONFIRM_CONFIG =
trim(`Confirm when unlocking FarmBot after an emergency stop.`);
export const DISCARD_UNSAVED_SEQUENCE_CHANGES =
trim(`Don't ask about saving sequence work before
closing browser tab. Warning: may cause loss of data.`);
export const USER_INTERFACE_READ_ONLY_MODE =
trim(`Disallow account data changes. This does
not prevent Farmwares or FarmBot OS from changing settings.`);
export const DISCARD_UNSAVED_SEQUENCE_CHANGES_CONFIRM =
trim(`Warning! When enabled, any unsaved changes to sequences
will be discarded when refreshing or closing the page. Are you sure?`);
export const CONFIRM_EMERGENCY_UNLOCK_CONFIRM_DISABLE =
trim(`Warning! When disabled, clicking the UNLOCK button will immediately
unlock FarmBot instead of confirming that it is safe to do so.
As a result, double-clicking the E-STOP button may not stop FarmBot.
Are you sure you want to disable this feature?`);
// Farm Designer Settings
export const PLANT_ANIMATIONS =
trim(`Enable plant animations in the garden map.`);
export const VIRTUAL_TRAIL =
trim(`Display a virtual trail for FarmBot in the garden map to show
movement and watering history while the map is open. Toggling this setting
will clear data for the current trail.`);
export const DYNAMIC_MAP_SIZE =
trim(`Change the garden map size based on axis length.
A value must be input in AXIS LENGTH and STOP AT MAX must be enabled in
the HARDWARE widget. Overrides MAP SIZE values.`);
export const MAP_SIZE =
trim(`Specify custom map dimensions (in millimeters).
@ -572,13 +602,41 @@ export namespace Content {
export const CONFIRM_PLANT_DELETION =
trim(`Show a confirmation dialog when deleting a plant.`);
// Device
export const NOT_HTTPS =
trim(`WARNING: Sending passwords via HTTP:// is not secure.`);
// FarmBot OS Settings
export const DIFFERENT_TZ_WARNING =
trim(`Note: The selected timezone for your FarmBot is different than
your local browser time.`);
export const CONTACT_SYSADMIN =
trim(`Please contact the system(s) administrator(s) and ask them to enable
HTTPS://`);
export const OS_BETA_RELEASES =
trim(`Warning! Leaving the stable FarmBot OS release channel may reduce
FarmBot system stability. Are you sure?`);
export const DEVICE_NEVER_SEEN =
trim(`The device has never been seen. Most likely,
there is a network connectivity issue on the device's end.`);
export const TOO_OLD_TO_UPDATE =
trim(`Please re-flash your FarmBot's SD card.`);
export const OS_AUTO_UPDATE =
trim(`When enabled, FarmBot OS will automatically download and install
software updates at the chosen time.`);
export const AUTO_SYNC =
trim(`When enabled, device resources such as sequences and regimens
will be sent to the device automatically. This removes the need to push
"SYNC" after making changes in the web app. Changes to running
sequences and regimens while auto sync is enabled will result in
instantaneous change.`);
// FarmBot OS Settings: Power and Reset
export const RESTART_FARMBOT =
trim(`This will restart FarmBot's Raspberry Pi and controller
software.`);
export const SHUTDOWN_FARMBOT =
trim(`This will shutdown FarmBot's Raspberry Pi. To turn it
back on, unplug FarmBot and plug it back in.`);
export const FACTORY_RESET_WARNING =
trim(`Factory resetting your FarmBot will destroy all data on the device,
@ -596,10 +654,6 @@ export namespace Content {
not delete data stored in your web app account. Are you sure you wish
to continue?`);
export const MCU_RESET_ALERT =
trim(`Warning: This will reset all hardware settings to the default values.
Are you sure you wish to continue?`);
export const AUTO_FACTORY_RESET =
trim(`Automatically factory reset when the WiFi network cannot be
detected. Useful for network changes.`);
@ -608,54 +662,26 @@ export namespace Content {
trim(`Time in minutes to attempt connecting to WiFi before a factory
reset.`);
export const DIFFERENT_TZ_WARNING =
trim(`Note: The selected timezone for your FarmBot is different than
your local browser time.`);
export const NOT_HTTPS =
trim(`WARNING: Sending passwords via HTTP:// is not secure.`);
export const RESTART_FARMBOT =
trim(`This will restart FarmBot's Raspberry Pi and controller
software.`);
export const CONTACT_SYSADMIN =
trim(`Please contact the system(s) administrator(s) and ask them to enable
HTTPS://`);
// FarmBot OS Settings: Firmware
export const RESTART_FIRMWARE =
trim(`Restart the Farmduino or Arduino firmware.`);
export const OS_AUTO_UPDATE =
trim(`When enabled, FarmBot OS will periodically check for, download,
and install updates automatically.`);
export const AUTO_SYNC =
trim(`When enabled, device resources such as sequences and regimens
will be sent to the device automatically. This removes the need to push
"SYNC" after making changes in the web app. Changes to running
sequences and regimens while auto sync is enabled will result in
instantaneous change.`);
export const SHUTDOWN_FARMBOT =
trim(`This will shutdown FarmBot's Raspberry Pi. To turn it
back on, unplug FarmBot and plug it back in.`);
export const OS_BETA_RELEASES =
trim(`Warning! Opting in to FarmBot OS beta releases may reduce
FarmBot system stability. Are you sure?`);
export const DIAGNOSTIC_CHECK =
trim(`Save snapshot of FarmBot OS system information, including
user and device identity, to the database. A code will be returned
that you can provide in support requests to allow FarmBot to look up
data relevant to the issue to help us identify the problem.`);
export const DEVICE_NEVER_SEEN =
trim(`The device has never been seen. Most likely,
there is a network connectivity issue on the device's end.`);
export const TOO_OLD_TO_UPDATE =
trim(`Please re-flash your FarmBot's SD card.`);
// Hardware Settings
// Hardware Settings: Danger Zone
export const RESTORE_DEFAULT_HARDWARE_SETTINGS =
trim(`Restoring hardware parameter defaults will destroy the
current settings, resetting them to default values.`);
export const MCU_RESET_ALERT =
trim(`Warning: This will reset all hardware settings to the default values.
Are you sure you wish to continue?`);
// App
export const APP_LOAD_TIMEOUT_MESSAGE =
trim(`App could not be fully loaded, we recommend you try
@ -674,19 +700,15 @@ export namespace Content {
trim(`FarmBot sent a malformed message. You may need to upgrade
FarmBot OS. Please upgrade FarmBot OS and log back in.`);
export const OLD_FBOS_REC_UPGRADE = trim(`Your version of FarmBot OS is
outdated and will soon no longer be supported. Please update your device as
soon as possible.`);
export const OLD_FBOS_REC_UPGRADE =
trim(`Your version of FarmBot OS is outdated and will soon no longer
be supported. Please update your device as soon as possible.`);
export const EXPERIMENTAL_WARNING =
trim(`Warning! This is an EXPERIMENTAL feature. This feature may be
broken and may break or otherwise hinder your usage of the rest of the
app. This feature may disappear or break at any time.`);
export const NEW_TOS =
trim(`Before logging in, you must agree to our latest Terms of Service and
Privacy Policy`);
export const FORCE_REFRESH_CONFIRM =
trim(`A new version of the FarmBot web app has been released.
Refresh page?`);
@ -715,8 +737,17 @@ export namespace Content {
export const END_DETECTION_DISABLED =
trim(`This command will not execute correctly because you do not have
encoders or endstops enabled for the chosen axis. Enable endstops or
encoders from the Device page for: `);
encoders, stall detection, or endstops enabled for the chosen axis.
Enable endstops, encoders, or stall detection from the Device page for: `);
export const REBOOT_STEP =
trim(`Power cycle FarmBot's onboard computer.`);
export const SHUTDOWN_STEP =
trim(`Power down FarmBot's onboard computer.`);
export const ESTOP_STEP =
trim(`Unlocking a device requires user intervention.`);
export const IN_USE =
trim(`Used in another resource. Protected from deletion.`);
@ -745,7 +776,7 @@ export namespace Content {
trim(`Click and drag or use the inputs to draw a weed.`);
export const BOX_SELECT_DESCRIPTION =
trim(`Drag a box around the plants you would like to select.
trim(`Drag a box around the items you would like to select.
Press the back arrow to exit.`);
export const SAVED_GARDENS =
@ -784,11 +815,15 @@ export namespace Content {
trim(`add this crop on OpenFarm?`);
export const NO_TOOLS =
trim(`Press "+" to add a new tool.`);
trim(`Press "+" to add a new tool or seed container.`);
export const NO_SEED_CONTAINERS =
trim(`Press "+" to add a seed container.`);
export const MOUNTED_TOOL =
trim(`The tool currently mounted to the UTM can be set here or by using
a MARK AS step in a sequence.`);
a MARK AS step in a sequence. Use the verify button or read the tool
verification pin in a sequence to verify that a tool is attached.`);
// Farm Events
export const NOTHING_SCHEDULED =
@ -800,10 +835,6 @@ export namespace Content {
regimen tasks. Consider rescheduling this event to tomorrow if
this is a concern.`);
export const INVALID_RUN_TIME =
trim(`This event does not appear to have a valid run time.
Perhaps you entered bad dates?`);
export const FARM_EVENT_TZ_WARNING =
trim(`Note: Times displayed according to FarmBot's local time, which
is currently different from your browser's time. Timezone data is
@ -818,27 +849,14 @@ export namespace Content {
trim(`You haven't made any sequences or regimens yet. To add an event,
first create a sequence or regimen.`);
// Groups
export const SORT_DESCRIPTION =
trim(`When executing a sequence over a Group of locations, FarmBot will
travel to each group member in the order of the chosen sort method.
If the random option is chosen, FarmBot will travel in a random order
every time, so the ordering shown below will only be representative.`);
export const CRITERIA_SELECTION_COUNT =
trim(`Criteria additions can only be removed by changing criteria.
Click and drag in the map to modify zone selection criteria.
Criteria will be applied at the time of sequence execution. The final
selection at that time may differ from the selection currently
displayed.`);
// Farmware
export const NO_IMAGES_YET =
trim(`You haven't yet taken any photos with your FarmBot.
Once you do, they will show up here.`);
export const PROCESSING_PHOTO =
trim(`Processing now. Results usually available in one minute.`);
trim(`Processing now. Results usually available in one minute.
Check log messages for result status.`);
export const NOT_AVAILABLE_WHEN_OFFLINE =
trim(`Not available when device is offline.`);
@ -859,12 +877,23 @@ export namespace TourContent {
selecting one, and dragging it into the garden.`);
export const ADD_TOOLS =
trim(`Press edit and then the + button to add tools and seed containers.`);
trim(`Press the + button to add tools and seed containers.`);
export const ADD_SEED_CONTAINERS =
trim(`Press the + button to add seed containers.`);
export const ADD_TOOLS_AND_SLOTS =
trim(`Press the + button to add tools and seed containers. Then create
slots for them to by pressing the slot + button.`);
export const ADD_SEED_CONTAINERS_AND_SLOTS =
trim(`Press the + button to add seed containers. Then create
slots for them to by pressing the slot + button.`);
export const ADD_TOOLS_SLOTS =
trim(`Add the newly created tools and seed containers to the
corresponding tool slots on FarmBot:
press edit and then + to create a tool slot.`);
corresponding slots on FarmBot:
press the + button to create a slot.`);
export const ADD_PERIPHERALS =
trim(`Press edit and then the + button to add peripherals.`);
@ -902,6 +931,112 @@ export namespace TourContent {
trim(`Toggle various settings to customize your web app experience.`);
}
export enum DeviceSetting {
axisHeadingLabels = ``,
// Homing and calibration
homingAndCalibration = `Homing and Calibration`,
homing = `Homing`,
calibration = `Calibration`,
setZeroPosition = `Set Zero Position`,
findHomeOnBoot = `Find Home on Boot`,
stopAtHome = `Stop at Home`,
stopAtMax = `Stop at Max`,
negativeCoordinatesOnly = `Negative Coordinates Only`,
axisLength = `Axis Length (mm)`,
// Motors
motors = `Motors`,
maxSpeed = `Max Speed (mm/s)`,
homingSpeed = `Homing Speed (mm/s)`,
minimumSpeed = `Minimum Speed (mm/s)`,
accelerateFor = `Accelerate for (mm)`,
stepsPerMm = `Steps per MM`,
microstepsPerStep = `Microsteps per step`,
alwaysPowerMotors = `Always Power Motors`,
invertMotors = `Invert Motors`,
motorCurrent = `Motor Current`,
enable2ndXMotor = `Enable 2nd X Motor`,
invert2ndXMotor = `Invert 2nd X Motor`,
// Encoders / Stall Detection
encoders = `Encoders`,
stallDetection = `Stall Detection`,
enableEncoders = `Enable Encoders`,
enableStallDetection = `Enable Stall Detection`,
stallSensitivity = `Stall Sensitivity`,
useEncodersForPositioning = `Use Encoders for Positioning`,
invertEncoders = `Invert Encoders`,
maxMissedSteps = `Max Missed Steps`,
missedStepDecay = `Missed Step Decay`,
encoderScaling = `Encoder Scaling`,
// Endstops
endstops = `Endstops`,
enableEndstops = `Enable Endstops`,
swapEndstops = `Swap Endstops`,
invertEndstops = `Invert Endstops`,
// Error handling
errorHandling = `Error Handling`,
timeoutAfter = `Timeout after (seconds)`,
maxRetries = `Max Retries`,
estopOnMovementError = `E-Stop on Movement Error`,
// Pin Guard
pinGuard = `Pin Guard`,
pinGuard1 = `Pin Guard 1`,
pinGuard2 = `Pin Guard 2`,
pinGuard3 = `Pin Guard 3`,
pinGuard4 = `Pin Guard 4`,
pinGuard5 = `Pin Guard 5`,
// Danger Zone
dangerZone = `Danger Zone`,
resetHardwareParams = `Reset hardware parameter defaults`,
// Pin Bindings
pinBindings = `Pin Bindings`,
savedPinBindings = `Saved pin bindings`,
addNewPinBinding = `Add new pin binding`,
// FarmBot OS
farmbot = `FarmBot`,
name = `name`,
timezone = `timezone`,
camera = `camera`,
firmware = `Firmware`,
applySoftwareUpdates = `update time`,
farmbotOSAutoUpdate = `auto update`,
farmbotOS = `Farmbot OS`,
autoSync = `Auto Sync`,
bootSequence = `Boot Sequence`,
// Power and Reset
powerAndReset = `Power and Reset`,
restartFarmbot = `Restart Farmbot`,
shutdownFarmbot = `Shutdown Farmbot`,
factoryReset = `Factory Reset`,
autoFactoryReset = `Automatic Factory Reset`,
connectionAttemptPeriod = `Connection Attempt Period`,
changeOwnership = `Change Ownership`,
// Farm Designer
farmDesigner = `Farm Designer`,
animations = `Plant animations`,
trail = `Virtual FarmBot trail`,
dynamicMap = `Dynamic map size`,
mapSize = `Map size`,
rotateMap = `Rotate map`,
mapOrigin = `Map origin`,
confirmPlantDeletion = `Confirm plant deletion`,
// Firmware
firmwareSection = `Firmware`,
restartFirmware = `Restart Firmware`,
flashFirmware = `Flash firmware`,
}
export namespace DiagnosticMessages {
export const OK = trim(`All systems nominal.`);
@ -924,8 +1059,7 @@ export namespace DiagnosticMessages {
but we have no recent record of FarmBot connecting to the internet.
This usually happens because of poor WiFi connectivity in the garden,
a bad password during configuration, a very long power outage, or
blocked ports on FarmBot's local network. Please refer IT staff to
https://software.farm.bot/docs/for-it-security-professionals`);
blocked ports on FarmBot's local network. Please refer IT staff to:`);
export const NO_WS_AVAILABLE = trim(`You are either offline, using a web
browser that does not support WebSockets, or are behind a firewall that
@ -989,6 +1123,8 @@ export enum Actions {
FETCH_BETA_OS_UPDATE_INFO_ERROR = "FETCH_BETA_OS_UPDATE_INFO_ERROR",
FETCH_MIN_OS_FEATURE_INFO_OK = "FETCH_MIN_OS_FEATURE_INFO_OK",
FETCH_MIN_OS_FEATURE_INFO_ERROR = "FETCH_MIN_OS_FEATURE_INFO_ERROR",
FETCH_OS_RELEASE_NOTES_OK = "FETCH_OS_RELEASE_NOTES_OK",
FETCH_OS_RELEASE_NOTES_ERROR = "FETCH_OS_RELEASE_NOTES_ERROR",
INVERT_JOG_BUTTON = "INVERT_JOG_BUTTON",
DISPLAY_ENCODER_DATA = "DISPLAY_ENCODER_DATA",
STASH_STATUS = "STASH_STATUS",
@ -999,7 +1135,8 @@ export enum Actions {
// Designer
SEARCH_QUERY_CHANGE = "SEARCH_QUERY_CHANGE",
SELECT_PLANT = "SELECT_PLANT",
SELECT_POINT = "SELECT_POINT",
SET_SELECTION_POINT_TYPE = "SET_SELECTION_POINT_TYPE",
TOGGLE_HOVERED_PLANT = "TOGGLE_HOVERED_PLANT",
TOGGLE_HOVERED_POINT = "TOGGLE_HOVERED_POINT",
HOVER_PLANT_LIST_ITEM = "HOVER_PLANT_LIST_ITEM",
@ -1008,9 +1145,12 @@ export enum Actions {
OF_SEARCH_RESULTS_OK = "OF_SEARCH_RESULTS_OK",
OF_SEARCH_RESULTS_NO = "OF_SEARCH_RESULTS_NO",
CHOOSE_LOCATION = "CHOOSE_LOCATION",
SET_CURRENT_POINT_DATA = "SET_CURRENT_POINT_DATA",
SET_DRAWN_POINT_DATA = "SET_DRAWN_POINT_DATA",
SET_DRAWN_WEED_DATA = "SET_DRAWN_WEED_DATA",
CHOOSE_SAVED_GARDEN = "CHOOSE_SAVED_GARDEN",
TRY_SORT_TYPE = "TRY_SORT_TYPE",
SET_SETTINGS_SEARCH_TERM = "SET_SETTINGS_SEARCH_TERM",
EDIT_GROUP_AREA_IN_MAP = "EDIT_GROUP_AREA_IN_MAP",
// Regimens
PUSH_WEEK = "PUSH_WEEK",

View File

@ -3,7 +3,7 @@ import { mount } from "enzyme";
import { RawControls as Controls } from "../controls";
import { bot } from "../../__test_support__/fake_state/bot";
import {
fakePeripheral, fakeWebcamFeed, fakeSensor
fakePeripheral, fakeWebcamFeed, fakeSensor,
} from "../../__test_support__/fake_state/resources";
import { Dictionary } from "farmbot";
import { Props } from "../interfaces";
@ -18,10 +18,9 @@ describe("<Controls />", () => {
feeds: [fakeWebcamFeed()],
peripherals: [fakePeripheral()],
sensors: [fakeSensor()],
botToMqttStatus: "up",
firmwareSettings: bot.hardware.mcu_params,
shouldDisplay: () => true,
getWebAppConfigVal: jest.fn((key) => (mockConfig[key])),
getWebAppConfigVal: jest.fn(key => mockConfig[key]),
sensorReadings: [],
timeSettings: fakeTimeSettings(),
env: {},
@ -65,6 +64,17 @@ describe("<Controls />", () => {
.map(string => expect(txt).not.toContain(string));
});
it("hides sensors widget based on model", () => {
mockConfig.hide_sensors = false;
const p = fakeProps();
p.firmwareHardware = "express_k10";
const wrapper = mount(<Controls {...p} />);
const txt = wrapper.text().toLowerCase();
["move", "peripherals"]
.map(string => expect(txt).toContain(string));
["sensors"].map(string => expect(txt).not.toContain(string));
});
it("doesn't show sensor readings widget", () => {
const p = fakeProps();
mockConfig.hide_sensors = true;

View File

@ -3,17 +3,19 @@ import { Row, Col } from "../ui/index";
import { AxisDisplayGroupProps } from "./interfaces";
import { isNumber } from "lodash";
import { t } from "../i18next_wrapper";
import { Xyz } from "farmbot";
const Axis = ({ val }: { val: number | undefined }) => <Col xs={3}>
<input disabled value={isNumber(val) ? val : "---"} />
</Col>;
const Axis = ({ axis, val }: { val: number | undefined, axis: Xyz }) =>
<Col xs={3}>
<input disabled name={axis} value={isNumber(val) ? val : "---"} />
</Col>;
export const AxisDisplayGroup = ({ position, label }: AxisDisplayGroupProps) => {
const { x, y, z } = position;
return <Row>
<Axis val={x} />
<Axis val={y} />
<Axis val={z} />
<Axis axis={"x"} val={x} />
<Axis axis={"y"} val={y} />
<Axis axis={"z"} val={z} />
<Col xs={3}>
<label>
{t(label)}

View File

@ -9,7 +9,8 @@ import { Props } from "./interfaces";
import { Move } from "./move/move";
import { BooleanSetting } from "../session_keys";
import { SensorReadings } from "./sensor_readings/sensor_readings";
import { isBotOnline } from "../devices/must_be_online";
import { isBotOnlineFromState } from "../devices/must_be_online";
import { hasSensors } from "../devices/components/firmware_hardware_support";
/** Controls page. */
export class RawControls extends React.Component<Props, {}> {
@ -18,13 +19,12 @@ export class RawControls extends React.Component<Props, {}> {
}
get botOnline() {
return isBotOnline(
this.props.bot.hardware.informational_settings.sync_status,
this.props.botToMqttStatus);
return isBotOnlineFromState(this.props.bot);
}
get hideSensors() {
return this.props.getWebAppConfigVal(BooleanSetting.hide_sensors);
return this.props.getWebAppConfigVal(BooleanSetting.hide_sensors)
|| !hasSensors(this.props.firmwareHardware);
}
move = () => <Move
@ -32,12 +32,12 @@ export class RawControls extends React.Component<Props, {}> {
env={this.props.env}
dispatch={this.props.dispatch}
arduinoBusy={this.arduinoBusy}
botToMqttStatus={this.props.botToMqttStatus}
firmwareSettings={this.props.firmwareSettings}
firmwareHardware={this.props.firmwareHardware}
getWebAppConfigVal={this.props.getWebAppConfigVal} />
peripherals = () => <Peripherals
firmwareHardware={this.props.firmwareHardware}
bot={this.props.bot}
peripherals={this.props.peripherals}
dispatch={this.props.dispatch}
@ -50,6 +50,7 @@ export class RawControls extends React.Component<Props, {}> {
sensors = () => this.hideSensors
? <div id="hidden-sensors-widget" />
: <Sensors
firmwareHardware={this.props.firmwareHardware}
bot={this.props.bot}
sensors={this.props.sensors}
dispatch={this.props.dispatch}

View File

@ -1,14 +1,13 @@
import {
BotState, Xyz, BotPosition, ShouldDisplay, UserEnv
BotState, Xyz, BotPosition, ShouldDisplay, UserEnv,
} from "../devices/interfaces";
import { Vector3, McuParams, FirmwareHardware } from "farmbot/dist";
import {
TaggedWebcamFeed,
TaggedPeripheral,
TaggedSensor,
TaggedSensorReading
TaggedSensorReading,
} from "farmbot";
import { NetworkState } from "../connectivity/interfaces";
import { GetWebAppConfigValue } from "../config_storage/actions";
import { TimeSettings } from "../interfaces";
@ -18,7 +17,6 @@ export interface Props {
feeds: TaggedWebcamFeed[];
peripherals: TaggedPeripheral[];
sensors: TaggedSensor[];
botToMqttStatus: NetworkState;
firmwareSettings: McuParams;
shouldDisplay: ShouldDisplay;
getWebAppConfigVal: GetWebAppConfigValue;
@ -60,4 +58,5 @@ export interface ToggleButtonProps {
dim?: boolean;
grayscale?: boolean;
title?: string;
className?: string;
}

View File

@ -15,12 +15,14 @@ export function KeyValEditRow(p: Props) {
return <Row>
<Col xs={6}>
<input type="text"
name="label"
placeholder={p.labelPlaceholder}
value={p.label}
onChange={p.onLabelChange} />
</Col>
<Col xs={4}>
<input type={p.valueType}
name="value"
value={p.value}
placeholder={p.valuePlaceholder}
onChange={p.onValueChange} />

View File

@ -1,5 +1,5 @@
import {
calcMicrostepsPerMm, calculateAxialLengths
calcMicrostepsPerMm, calculateAxialLengths,
} from "../direction_axes_props";
import { fakeFirmwareConfig } from "../../../__test_support__/fake_state/resources";

View File

@ -9,7 +9,7 @@ jest.mock("../../../device", () => ({
import * as React from "react";
import { mount } from "enzyme";
import {
DirectionButton, directionDisabled, calculateDistance
DirectionButton, directionDisabled, calculateDistance,
} from "../direction_button";
import { DirectionButtonProps } from "../interfaces";

View File

@ -29,7 +29,6 @@ describe("<Move />", () => {
dispatch: jest.fn(),
bot: bot,
arduinoBusy: false,
botToMqttStatus: "up",
firmwareSettings: bot.hardware.mcu_params,
getWebAppConfigVal: jest.fn((key) => (mockConfig[key])),
env: {},

View File

@ -9,7 +9,7 @@ import * as React from "react";
import { mount } from "enzyme";
import { BooleanSetting } from "../../../session_keys";
import {
moveWidgetSetting, MoveWidgetSettingsMenu, MoveWidgetSettingsMenuProps
moveWidgetSetting, MoveWidgetSettingsMenu, MoveWidgetSettingsMenuProps,
} from "../settings_menu";
describe("moveWidgetSetting()", () => {

View File

@ -7,7 +7,7 @@ import { AxisInputBoxGroup } from "../axis_input_box_group";
import { GetWebAppBool } from "./interfaces";
import { BooleanSetting } from "../../session_keys";
import { t } from "../../i18next_wrapper";
import { isExpressBoard } from "../../devices/components/firmware_hardware_support";
import { hasEncoders } from "../../devices/components/firmware_hardware_support";
import { FirmwareHardware } from "farmbot";
export interface BotPositionRowsProps {
@ -19,7 +19,7 @@ export interface BotPositionRowsProps {
export const BotPositionRows = (props: BotPositionRowsProps) => {
const { locationData, getValue, arduinoBusy } = props;
return <div>
return <div className={"bot-position-rows"}>
<Row>
<Col xs={3}>
<label>{t("X AXIS")}</label>
@ -34,12 +34,12 @@ export const BotPositionRows = (props: BotPositionRowsProps) => {
<AxisDisplayGroup
position={locationData.position}
label={t("Motor Coordinates (mm)")} />
{!isExpressBoard(props.firmwareHardware) &&
{hasEncoders(props.firmwareHardware) &&
getValue(BooleanSetting.scaled_encoders) &&
<AxisDisplayGroup
position={locationData.scaled_encoders}
label={t("Scaled Encoder (mm)")} />}
{!isExpressBoard(props.firmwareHardware) &&
{hasEncoders(props.firmwareHardware) &&
getValue(BooleanSetting.raw_encoders) &&
<AxisDisplayGroup
position={locationData.raw_encoders}

View File

@ -1,6 +1,5 @@
import { BotPosition, BotState, UserEnv } from "../../devices/interfaces";
import { McuParams, Xyz, FirmwareHardware } from "farmbot";
import { NetworkState } from "../../connectivity/interfaces";
import { GetWebAppConfigValue } from "../../config_storage/actions";
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
@ -11,7 +10,6 @@ export interface MoveProps {
dispatch: Function;
bot: BotState;
arduinoBusy: boolean;
botToMqttStatus: NetworkState;
firmwareSettings: McuParams;
getWebAppConfigVal: GetWebAppConfigValue;
env: UserEnv;

View File

@ -6,7 +6,7 @@ import { getDevice } from "../../device";
import { buildDirectionProps } from "./direction_axes_props";
import { t } from "../../i18next_wrapper";
import {
cameraBtnProps
cameraBtnProps,
} from "../../devices/components/fbos_settings/camera_selection";
const DEFAULT_STEP_SIZE = 100;

View File

@ -22,7 +22,7 @@ export const JogControlsGroup = (props: JogControlsGroupProps) => {
const {
dispatch, stepSize, botPosition, getValue, arduinoBusy, firmwareSettings
} = props;
return <div>
return <div className={"jog-controls-group"}>
<label className="text-center">
{t("MOVE AMOUNT (mm)")}
</label>

View File

@ -4,7 +4,7 @@ import moment from "moment";
import { BotLocationData, BotPosition } from "../../devices/interfaces";
import { trim } from "../../util";
import {
cloneDeep, max, get, isNumber, isEqual, takeRight, ceil, range
cloneDeep, max, get, isNumber, isEqual, takeRight, ceil, range,
} from "lodash";
import { t } from "../../i18next_wrapper";
@ -46,9 +46,9 @@ const getLastEntry = (): Entry | undefined => {
const findYLimit = (): number => {
const array = getArray();
const arrayAbsMax = max(array.map(entry =>
max(["position", "scaled_encoders"].map((name: LocationName) =>
max(["position", "scaled_encoders"].map((key: LocationName) =>
max(["x", "y", "z"].map((axis: Xyz) =>
Math.abs(entry.locationData[name][axis] || 0) + 1))))));
Math.abs(entry.locationData[key][axis] || 0) + 1))))));
return Math.max(ceil(arrayAbsMax || 0, -2), DEFAULT_Y_MAX);
};
@ -80,19 +80,19 @@ const getPaths = (): Paths => {
const paths = newPaths();
if (last) {
getReversedArray().map(entry => {
["position", "scaled_encoders"].map((name: LocationName) => {
["position", "scaled_encoders"].map((key: LocationName) => {
["x", "y", "z"].map((axis: Xyz) => {
const lastPos = last.locationData[name][axis];
const pos = entry.locationData[name][axis];
const lastPos = last.locationData[key][axis];
const pos = entry.locationData[key][axis];
if (isNumber(lastPos) && isFinite(lastPos)
&& isNumber(maxY) && isNumber(pos)) {
if (!paths[name][axis].startsWith("M")) {
if (!paths[key][axis].startsWith("M")) {
const yStart = -lastPos / maxY * HEIGHT / 2;
paths[name][axis] = `M ${MAX_X},${yStart} `;
paths[key][axis] = `M ${MAX_X},${yStart} `;
}
const x = MAX_X - (last.timestamp - entry.timestamp);
const y = -pos / maxY * HEIGHT / 2;
paths[name][axis] += `L ${x},${y} `;
paths[key][axis] += `L ${x},${y} `;
}
});
});
@ -154,12 +154,12 @@ const PlotLines = ({ locationData }: { locationData: BotLocationData }) => {
updateArray({ timestamp: moment().unix(), locationData });
const paths = getPaths();
return <g id="plot_lines">
{["position", "scaled_encoders"].map((name: LocationName) =>
{["position", "scaled_encoders"].map((key: LocationName) =>
["x", "y", "z"].map((axis: Xyz) =>
<path key={name + axis} fill={"none"}
stroke={COLOR_LOOKUP[axis]} strokeWidth={LINEWIDTH_LOOKUP[name]}
<path key={key + axis} fill={"none"}
stroke={COLOR_LOOKUP[axis]} strokeWidth={LINEWIDTH_LOOKUP[key]}
strokeLinecap={"round"} strokeLinejoin={"round"}
d={paths[name][axis]} />))}
d={paths[key][axis]} />))}
</g>;
};

View File

@ -13,6 +13,7 @@ import { MotorPositionPlot } from "./motor_position_plot";
import { Popover, Position } from "@blueprintjs/core";
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
import { t } from "../../i18next_wrapper";
import { getStatus } from "../../connectivity/reducer_support";
export class Move extends React.Component<MoveProps, {}> {
@ -23,7 +24,8 @@ export class Move extends React.Component<MoveProps, {}> {
!!this.props.getWebAppConfigVal(BooleanSetting[key]);
render() {
const { location_data, informational_settings } = this.props.bot.hardware;
const { bot } = this.props;
const { location_data, informational_settings } = bot.hardware;
const locationData = validBotLocationData(location_data);
return <Widget className="move-widget">
<WidgetHeader
@ -40,11 +42,11 @@ export class Move extends React.Component<MoveProps, {}> {
<WidgetBody>
<MustBeOnline
lockOpen={process.env.NODE_ENV !== "production"}
networkState={this.props.botToMqttStatus}
networkState={getStatus(bot.connectivity.uptime["bot.mqtt"])}
syncStatus={informational_settings.sync_status}>
<JogControlsGroup
dispatch={this.props.dispatch}
stepSize={this.props.bot.stepSize}
stepSize={bot.stepSize}
botPosition={locationData.position}
getValue={this.getValue}
arduinoBusy={this.props.arduinoBusy}

View File

@ -6,7 +6,7 @@ import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
import { DevSettings } from "../../account/dev/dev_support";
import { t } from "../../i18next_wrapper";
import { FirmwareHardware } from "farmbot";
import { isExpressBoard } from "../../devices/components/firmware_hardware_support";
import { hasEncoders } from "../../devices/components/firmware_hardware_support";
export const moveWidgetSetting =
(toggle: ToggleWebAppBool, getValue: GetWebAppBool) =>
@ -27,7 +27,7 @@ export interface MoveWidgetSettingsMenuProps {
}
export const MoveWidgetSettingsMenu = (
{ toggle, getValue, firmwareHardware }: MoveWidgetSettingsMenuProps
{ toggle, getValue, firmwareHardware }: MoveWidgetSettingsMenuProps,
) => {
const Setting = moveWidgetSetting(toggle, getValue);
return <div className="move-settings-menu">
@ -36,7 +36,7 @@ export const MoveWidgetSettingsMenu = (
<Setting label={t("Y Axis")} setting={BooleanSetting.y_axis_inverted} />
<Setting label={t("Z Axis")} setting={BooleanSetting.z_axis_inverted} />
{!isExpressBoard(firmwareHardware) &&
{hasEncoders(firmwareHardware) &&
<div className="display-encoder-data">
<p>{t("Display Encoder Data")}</p>
<Setting
@ -56,7 +56,7 @@ export const MoveWidgetSettingsMenu = (
setting={BooleanSetting.home_button_homing} />
{DevSettings.futureFeaturesEnabled() &&
<div>
<div className={"motor-position-plot-setting-row"}>
<p>{t("Motor position plot")}</p>
<Setting
label={t("show")}

Some files were not shown because too many files have changed in this diff Show More