Merge branch 'staging' into issue-1685
commit
dd46830b9d
2
Gemfile
2
Gemfile
|
@ -24,7 +24,7 @@ gem "scenic"
|
||||||
gem "secure_headers"
|
gem "secure_headers"
|
||||||
gem "tzinfo" # For validation of user selected timezone names
|
gem "tzinfo" # For validation of user selected timezone names
|
||||||
gem "valid_url"
|
gem "valid_url"
|
||||||
# gem "farady", "~> 1.0.0"
|
gem "kaminari"
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem "climate_control"
|
gem "climate_control"
|
||||||
|
|
46
Gemfile.lock
46
Gemfile.lock
|
@ -72,7 +72,7 @@ GEM
|
||||||
amq-protocol (2.3.0)
|
amq-protocol (2.3.0)
|
||||||
bcrypt (3.1.13)
|
bcrypt (3.1.13)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
bunny (2.14.3)
|
bunny (2.14.4)
|
||||||
amq-protocol (~> 2.3, >= 2.3.0)
|
amq-protocol (~> 2.3, >= 2.3.0)
|
||||||
case_transform (0.2)
|
case_transform (0.2)
|
||||||
activesupport
|
activesupport
|
||||||
|
@ -82,9 +82,9 @@ GEM
|
||||||
simplecov
|
simplecov
|
||||||
url
|
url
|
||||||
coderay (1.1.2)
|
coderay (1.1.2)
|
||||||
concurrent-ruby (1.1.5)
|
concurrent-ruby (1.1.6)
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
database_cleaner (1.7.0)
|
database_cleaner (1.8.3)
|
||||||
declarative (0.0.10)
|
declarative (0.0.10)
|
||||||
declarative-option (0.1.0)
|
declarative-option (0.1.0)
|
||||||
delayed_job (4.1.8)
|
delayed_job (4.1.8)
|
||||||
|
@ -100,7 +100,7 @@ GEM
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
diff-lcs (1.3)
|
diff-lcs (1.3)
|
||||||
digest-crc (0.4.1)
|
digest-crc (0.4.1)
|
||||||
discard (1.1.0)
|
discard (1.2.0)
|
||||||
activerecord (>= 4.2, < 7)
|
activerecord (>= 4.2, < 7)
|
||||||
docile (1.3.2)
|
docile (1.3.2)
|
||||||
erubi (1.9.0)
|
erubi (1.9.0)
|
||||||
|
@ -109,7 +109,7 @@ GEM
|
||||||
factory_bot_rails (5.1.1)
|
factory_bot_rails (5.1.1)
|
||||||
factory_bot (~> 5.1.0)
|
factory_bot (~> 5.1.0)
|
||||||
railties (>= 4.2.0)
|
railties (>= 4.2.0)
|
||||||
faker (2.10.1)
|
faker (2.10.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
faraday (0.15.4)
|
faraday (0.15.4)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
|
@ -119,7 +119,7 @@ GEM
|
||||||
railties (>= 3.2, < 6.1)
|
railties (>= 3.2, < 6.1)
|
||||||
globalid (0.4.2)
|
globalid (0.4.2)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
google-api-client (0.36.4)
|
google-api-client (0.37.1)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (~> 0.9)
|
googleauth (~> 0.9)
|
||||||
httpclient (>= 2.8.1, < 3.0)
|
httpclient (>= 2.8.1, < 3.0)
|
||||||
|
@ -127,10 +127,12 @@ GEM
|
||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.0)
|
retriable (>= 2.0, < 4.0)
|
||||||
signet (~> 0.12)
|
signet (~> 0.12)
|
||||||
google-cloud-core (1.4.1)
|
google-cloud-core (1.5.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.3.0)
|
google-cloud-env (1.3.0)
|
||||||
faraday (~> 0.11)
|
faraday (~> 0.11)
|
||||||
|
google-cloud-errors (1.0.0)
|
||||||
google-cloud-storage (1.25.1)
|
google-cloud-storage (1.25.1)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
|
@ -153,6 +155,18 @@ GEM
|
||||||
json (2.3.0)
|
json (2.3.0)
|
||||||
jsonapi-renderer (0.2.2)
|
jsonapi-renderer (0.2.2)
|
||||||
jwt (2.2.1)
|
jwt (2.2.1)
|
||||||
|
kaminari (1.2.0)
|
||||||
|
activesupport (>= 4.1.0)
|
||||||
|
kaminari-actionview (= 1.2.0)
|
||||||
|
kaminari-activerecord (= 1.2.0)
|
||||||
|
kaminari-core (= 1.2.0)
|
||||||
|
kaminari-actionview (1.2.0)
|
||||||
|
actionview
|
||||||
|
kaminari-core (= 1.2.0)
|
||||||
|
kaminari-activerecord (1.2.0)
|
||||||
|
activerecord
|
||||||
|
kaminari-core (= 1.2.0)
|
||||||
|
kaminari-core (1.2.0)
|
||||||
loofah (2.4.0)
|
loofah (2.4.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
|
@ -162,7 +176,7 @@ GEM
|
||||||
mimemagic (~> 0.3.2)
|
mimemagic (~> 0.3.2)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
method_source (0.9.2)
|
method_source (0.9.2)
|
||||||
mimemagic (0.3.3)
|
mimemagic (0.3.4)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.0.2)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
minitest (5.14.0)
|
minitest (5.14.0)
|
||||||
|
@ -171,7 +185,7 @@ GEM
|
||||||
mutations (0.9.0)
|
mutations (0.9.0)
|
||||||
activesupport
|
activesupport
|
||||||
nio4r (2.5.2)
|
nio4r (2.5.2)
|
||||||
nokogiri (1.10.7)
|
nokogiri (1.10.8)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
os (1.0.1)
|
os (1.0.1)
|
||||||
|
@ -190,7 +204,7 @@ GEM
|
||||||
faraday_middleware (~> 0.13.0)
|
faraday_middleware (~> 0.13.0)
|
||||||
hashie (~> 3.6)
|
hashie (~> 3.6)
|
||||||
multi_json (~> 1.13.1)
|
multi_json (~> 1.13.1)
|
||||||
rack (2.1.1)
|
rack (2.2.2)
|
||||||
rack-attack (6.2.2)
|
rack-attack (6.2.2)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rack-cors (1.1.1)
|
rack-cors (1.1.1)
|
||||||
|
@ -240,7 +254,7 @@ GEM
|
||||||
actionpack (>= 5.0)
|
actionpack (>= 5.0)
|
||||||
railties (>= 5.0)
|
railties (>= 5.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rollbar (2.23.2)
|
rollbar (2.24.0)
|
||||||
rspec (3.9.0)
|
rspec (3.9.0)
|
||||||
rspec-core (~> 3.9.0)
|
rspec-core (~> 3.9.0)
|
||||||
rspec-expectations (~> 3.9.0)
|
rspec-expectations (~> 3.9.0)
|
||||||
|
@ -264,7 +278,7 @@ GEM
|
||||||
rspec-support (3.9.2)
|
rspec-support (3.9.2)
|
||||||
rspec_junit_formatter (0.4.1)
|
rspec_junit_formatter (0.4.1)
|
||||||
rspec-core (>= 2, < 4, != 2.12.0)
|
rspec-core (>= 2, < 4, != 2.12.0)
|
||||||
scenic (1.5.1)
|
scenic (1.5.2)
|
||||||
activerecord (>= 4.0.0)
|
activerecord (>= 4.0.0)
|
||||||
railties (>= 4.0.0)
|
railties (>= 4.0.0)
|
||||||
secure_headers (6.3.0)
|
secure_headers (6.3.0)
|
||||||
|
@ -273,11 +287,10 @@ GEM
|
||||||
faraday (~> 0.9)
|
faraday (~> 0.9)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
simplecov (0.17.1)
|
simplecov (0.18.5)
|
||||||
docile (~> 1.1)
|
docile (~> 1.1)
|
||||||
json (>= 1.8, < 3)
|
simplecov-html (~> 0.11)
|
||||||
simplecov-html (~> 0.10.0)
|
simplecov-html (0.12.1)
|
||||||
simplecov-html (0.10.2)
|
|
||||||
sprockets (4.0.0)
|
sprockets (4.0.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
|
@ -320,6 +333,7 @@ DEPENDENCIES
|
||||||
google-cloud-storage (~> 1.11)
|
google-cloud-storage (~> 1.11)
|
||||||
hashdiff
|
hashdiff
|
||||||
jwt
|
jwt
|
||||||
|
kaminari
|
||||||
mutations
|
mutations
|
||||||
passenger
|
passenger
|
||||||
pg
|
pg
|
||||||
|
|
|
@ -80,6 +80,16 @@ module Api
|
||||||
{ root: false, user: current_user }
|
{ root: false, user: current_user }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def maybe_paginate(collection)
|
||||||
|
page = params[:page]
|
||||||
|
per = params[:per]
|
||||||
|
|
||||||
|
if page && per
|
||||||
|
render json: collection.page(page).per(per)
|
||||||
|
else
|
||||||
|
render json: collection
|
||||||
|
end
|
||||||
|
end
|
||||||
private
|
private
|
||||||
|
|
||||||
def clean_expired_farm_events
|
def clean_expired_farm_events
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module Api
|
module Api
|
||||||
class AlertsController < Api::AbstractController
|
class AlertsController < Api::AbstractController
|
||||||
def index
|
def index
|
||||||
render json: current_device.alerts
|
maybe_paginate current_device.alerts
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
|
|
@ -3,7 +3,7 @@ module Api
|
||||||
before_action :clean_expired_farm_events, only: [:index]
|
before_action :clean_expired_farm_events, only: [:index]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: current_device.farm_events
|
maybe_paginate current_device.farm_events
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -10,7 +10,7 @@ module Api
|
||||||
end
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: farmware_envs
|
maybe_paginate farmware_envs
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module Api
|
module Api
|
||||||
class FarmwareInstallationsController < Api::AbstractController
|
class FarmwareInstallationsController < Api::AbstractController
|
||||||
def index
|
def index
|
||||||
render json: farmware_installations
|
maybe_paginate farmware_installations
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module Api
|
module Api
|
||||||
class PeripheralsController < Api::AbstractController
|
class PeripheralsController < Api::AbstractController
|
||||||
def index
|
def index
|
||||||
render json: current_device.peripherals
|
maybe_paginate current_device.peripherals
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module Api
|
module Api
|
||||||
class PinBindingsController < Api::AbstractController
|
class PinBindingsController < Api::AbstractController
|
||||||
def index
|
def index
|
||||||
render json: pin_bindings
|
maybe_paginate pin_bindings
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module Api
|
module Api
|
||||||
class PlantTemplatesController < Api::AbstractController
|
class PlantTemplatesController < Api::AbstractController
|
||||||
def index
|
def index
|
||||||
render json: current_device.plant_templates
|
maybe_paginate current_device.plant_templates
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
|
|
@ -3,7 +3,7 @@ module Api
|
||||||
before_action :clean_expired_farm_events, only: [:destroy]
|
before_action :clean_expired_farm_events, only: [:destroy]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: your_point_groups
|
maybe_paginate your_point_groups
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -20,7 +20,7 @@ module Api
|
||||||
.where("discarded_at < ?", Time.now - HARD_DELETE_AFTER)
|
.where("discarded_at < ?", Time.now - HARD_DELETE_AFTER)
|
||||||
.destroy_all
|
.destroy_all
|
||||||
|
|
||||||
render json: points(params.fetch(:filter) { "kept" })
|
maybe_paginate points(params.fetch(:filter) { "kept" })
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -3,7 +3,7 @@ module Api
|
||||||
before_action :clean_expired_farm_events, only: [:destroy]
|
before_action :clean_expired_farm_events, only: [:destroy]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: your_regimens
|
maybe_paginate your_regimens
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module Api
|
module Api
|
||||||
class SavedGardensController < Api::AbstractController
|
class SavedGardensController < Api::AbstractController
|
||||||
def index
|
def index
|
||||||
render json: current_device.saved_gardens
|
maybe_paginate current_device.saved_gardens
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
module Api
|
module Api
|
||||||
class SensorReadingsController < Api::AbstractController
|
class SensorReadingsController < Api::AbstractController
|
||||||
|
LIMIT = 5000
|
||||||
|
before_action :clean_old
|
||||||
|
|
||||||
def create
|
def create
|
||||||
mutate SensorReadings::Create.run(raw_json, device: current_device)
|
mutate SensorReadings::Create.run(raw_json, device: current_device)
|
||||||
end
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: readings
|
maybe_paginate(readings)
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@ -17,10 +20,23 @@ module Api
|
||||||
render json: ""
|
render json: ""
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def clean_old
|
||||||
|
if current_device.sensor_readings.count > LIMIT
|
||||||
|
current_device
|
||||||
|
.sensor_readings
|
||||||
|
.where
|
||||||
|
.not(id: readings.pluck(:id))
|
||||||
|
.delete_all
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def readings
|
def readings
|
||||||
SensorReading.where(device: current_device)
|
@readings ||= SensorReading
|
||||||
|
.where(device: current_device)
|
||||||
|
.order(created_at: :desc)
|
||||||
|
.limit(LIMIT)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reading
|
def reading
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module Api
|
module Api
|
||||||
class SensorsController < Api::AbstractController
|
class SensorsController < Api::AbstractController
|
||||||
def index
|
def index
|
||||||
render json: current_device.sensors
|
maybe_paginate current_device.sensors
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -2,7 +2,7 @@ module Api
|
||||||
class ToolsController < Api::AbstractController
|
class ToolsController < Api::AbstractController
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: tools
|
maybe_paginate tools
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -7,7 +7,7 @@ module Api
|
||||||
end
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: webcams
|
maybe_paginate webcams
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -171,9 +171,10 @@ class Device < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
TOO_MANY_CONNECTIONS =
|
TOO_MANY_CONNECTIONS =
|
||||||
"Your device is " +
|
"Your device is reconnecting to the server too often. " +
|
||||||
"reconnecting to the server too often. Please " +
|
"This may be a sign of local network issues. " +
|
||||||
"see https://developer.farm.bot/docs/connectivity-issues"
|
"Please review the documentation provided at " +
|
||||||
|
"https://software.farm.bot/docs/connecting-farmbot-to-the-internet"
|
||||||
def self.connection_warning(username)
|
def self.connection_warning(username)
|
||||||
device_id = username.split("_").last.to_i || 0
|
device_id = username.split("_").last.to_i || 0
|
||||||
device = self.find_by(id: device_id)
|
device = self.find_by(id: device_id)
|
||||||
|
|
|
@ -6,7 +6,7 @@ class InUsePoint < ApplicationRecord
|
||||||
DEFAULT_NAME = "point"
|
DEFAULT_NAME = "point"
|
||||||
FANCY_NAMES = {
|
FANCY_NAMES = {
|
||||||
GenericPointer.name => DEFAULT_NAME,
|
GenericPointer.name => DEFAULT_NAME,
|
||||||
ToolSlot.name => "tool slot",
|
ToolSlot.name => "slot",
|
||||||
Plant.name => "plant",
|
Plant.name => "plant",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ class PointGroup < ApplicationRecord
|
||||||
BAD_SORT = "%{value} is not valid. Valid options are: " +
|
BAD_SORT = "%{value} is not valid. Valid options are: " +
|
||||||
SORT_TYPES.map(&:inspect).join(", ")
|
SORT_TYPES.map(&:inspect).join(", ")
|
||||||
DEFAULT_CRITERIA = {
|
DEFAULT_CRITERIA = {
|
||||||
day: { op: "<", days: 0 },
|
day: { op: "<", days_ago: 0 },
|
||||||
string_eq: {},
|
string_eq: {},
|
||||||
number_eq: {},
|
number_eq: {},
|
||||||
number_lt: {},
|
number_lt: {},
|
||||||
|
|
|
@ -11,7 +11,7 @@ class ToolSlot < Point
|
||||||
MIN_PULLOUT = PULLOUT_DIRECTIONS.min
|
MIN_PULLOUT = PULLOUT_DIRECTIONS.min
|
||||||
PULLOUT_ERR = "must be a value between #{MIN_PULLOUT} and #{MAX_PULLOUT}. "\
|
PULLOUT_ERR = "must be a value between #{MIN_PULLOUT} and #{MAX_PULLOUT}. "\
|
||||||
"%{value} is not valid."
|
"%{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"\
|
"Please un-assign the tool from its current slot"\
|
||||||
" before reassigning."
|
" before reassigning."
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,9 @@ module Devices
|
||||||
"genesis_1.2" => Devices::Seeders::GenesisOneTwo,
|
"genesis_1.2" => Devices::Seeders::GenesisOneTwo,
|
||||||
"genesis_1.3" => Devices::Seeders::GenesisOneThree,
|
"genesis_1.3" => Devices::Seeders::GenesisOneThree,
|
||||||
"genesis_1.4" => Devices::Seeders::GenesisOneFour,
|
"genesis_1.4" => Devices::Seeders::GenesisOneFour,
|
||||||
|
"genesis_1.5" => Devices::Seeders::GenesisOneFive,
|
||||||
"genesis_xl_1.4" => Devices::Seeders::GenesisXlOneFour,
|
"genesis_xl_1.4" => Devices::Seeders::GenesisXlOneFour,
|
||||||
|
"genesis_xl_1.5" => Devices::Seeders::GenesisXlOneFive,
|
||||||
|
|
||||||
"demo_account" => Devices::Seeders::DemoAccountSeeder,
|
"demo_account" => Devices::Seeders::DemoAccountSeeder,
|
||||||
"none" => Devices::Seeders::None,
|
"none" => Devices::Seeders::None,
|
||||||
|
|
|
@ -27,7 +27,7 @@ module Devices
|
||||||
add_tool_slot(name: ToolNames::SEED_TROUGH_1,
|
add_tool_slot(name: ToolNames::SEED_TROUGH_1,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 25,
|
y: 25,
|
||||||
z: -200,
|
z: 0,
|
||||||
tool: tools_seed_trough_1,
|
tool: tools_seed_trough_1,
|
||||||
pullout_direction: ToolSlot::NONE,
|
pullout_direction: ToolSlot::NONE,
|
||||||
gantry_mounted: true)
|
gantry_mounted: true)
|
||||||
|
@ -37,25 +37,18 @@ module Devices
|
||||||
add_tool_slot(name: ToolNames::SEED_TROUGH_2,
|
add_tool_slot(name: ToolNames::SEED_TROUGH_2,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 50,
|
y: 50,
|
||||||
z: -200,
|
z: 0,
|
||||||
tool: tools_seed_trough_2,
|
tool: tools_seed_trough_2,
|
||||||
pullout_direction: ToolSlot::NONE,
|
pullout_direction: ToolSlot::NONE,
|
||||||
gantry_mounted: true)
|
gantry_mounted: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def tool_slots_slot_3
|
def tool_slots_slot_3; end
|
||||||
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_4; end
|
def tool_slots_slot_4; end
|
||||||
def tool_slots_slot_5; end
|
def tool_slots_slot_5; end
|
||||||
def tool_slots_slot_6; 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_bin; end
|
||||||
def tools_seed_tray; end
|
def tools_seed_tray; end
|
||||||
|
|
||||||
|
@ -69,11 +62,6 @@ module Devices
|
||||||
add_tool(ToolNames::SEED_TROUGH_2)
|
add_tool(ToolNames::SEED_TROUGH_2)
|
||||||
end
|
end
|
||||||
|
|
||||||
def tools_seed_trough_3
|
|
||||||
@tools_seed_trough_3 ||=
|
|
||||||
add_tool(ToolNames::SEED_TROUGH_3)
|
|
||||||
end
|
|
||||||
|
|
||||||
def tools_seeder; end
|
def tools_seeder; end
|
||||||
def tools_soil_sensor; end
|
def tools_soil_sensor; end
|
||||||
def tools_watering_nozzle; end
|
def tools_watering_nozzle; end
|
||||||
|
|
|
@ -75,6 +75,9 @@ module Devices
|
||||||
tool: tools_weeder)
|
tool: tools_weeder)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def tool_slots_slot_7; end
|
||||||
|
def tool_slots_slot_8; end
|
||||||
|
|
||||||
def tools_seed_bin
|
def tools_seed_bin
|
||||||
@tools_seed_bin ||=
|
@tools_seed_bin ||=
|
||||||
add_tool(ToolNames::SEED_BIN)
|
add_tool(ToolNames::SEED_BIN)
|
||||||
|
@ -87,7 +90,6 @@ module Devices
|
||||||
|
|
||||||
def tools_seed_trough_1; end
|
def tools_seed_trough_1; end
|
||||||
def tools_seed_trough_2; end
|
def tools_seed_trough_2; end
|
||||||
def tools_seed_trough_3; end
|
|
||||||
|
|
||||||
def tools_seeder
|
def tools_seeder
|
||||||
@tools_seeder ||=
|
@tools_seeder ||=
|
||||||
|
|
|
@ -37,7 +37,6 @@ module Devices
|
||||||
:tools_seed_tray,
|
:tools_seed_tray,
|
||||||
:tools_seed_trough_1,
|
:tools_seed_trough_1,
|
||||||
:tools_seed_trough_2,
|
:tools_seed_trough_2,
|
||||||
:tools_seed_trough_3,
|
|
||||||
:tools_seeder,
|
:tools_seeder,
|
||||||
:tools_soil_sensor,
|
:tools_soil_sensor,
|
||||||
:tools_watering_nozzle,
|
:tools_watering_nozzle,
|
||||||
|
@ -50,6 +49,8 @@ module Devices
|
||||||
:tool_slots_slot_4,
|
:tool_slots_slot_4,
|
||||||
:tool_slots_slot_5,
|
:tool_slots_slot_5,
|
||||||
:tool_slots_slot_6,
|
:tool_slots_slot_6,
|
||||||
|
:tool_slots_slot_7,
|
||||||
|
:tool_slots_slot_8,
|
||||||
|
|
||||||
# WEBCAM FEEDS ===========================
|
# WEBCAM FEEDS ===========================
|
||||||
:webcam_feeds,
|
:webcam_feeds,
|
||||||
|
@ -152,11 +153,12 @@ module Devices
|
||||||
def tool_slots_slot_4; end
|
def tool_slots_slot_4; end
|
||||||
def tool_slots_slot_5; end
|
def tool_slots_slot_5; end
|
||||||
def tool_slots_slot_6; 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_bin; end
|
||||||
def tools_seed_tray; end
|
def tools_seed_tray; end
|
||||||
def tools_seed_trough_1; end
|
def tools_seed_trough_1; end
|
||||||
def tools_seed_trough_2; end
|
def tools_seed_trough_2; end
|
||||||
def tools_seed_trough_3; end
|
|
||||||
def tools_seeder; end
|
def tools_seeder; end
|
||||||
def tools_soil_sensor; end
|
def tools_soil_sensor; end
|
||||||
def tools_watering_nozzle; end
|
def tools_watering_nozzle; end
|
||||||
|
|
|
@ -31,7 +31,6 @@ module Devices
|
||||||
LIGHTING = "Lighting"
|
LIGHTING = "Lighting"
|
||||||
SEED_TROUGH_1 = "Seed Trough 1"
|
SEED_TROUGH_1 = "Seed Trough 1"
|
||||||
SEED_TROUGH_2 = "Seed Trough 2"
|
SEED_TROUGH_2 = "Seed Trough 2"
|
||||||
SEED_TROUGH_3 = "Seed Trough 3"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Stub plants ==============================
|
# Stub plants ==============================
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -28,11 +28,12 @@ module Devices
|
||||||
def tool_slots_slot_4; end
|
def tool_slots_slot_4; end
|
||||||
def tool_slots_slot_5; end
|
def tool_slots_slot_5; end
|
||||||
def tool_slots_slot_6; 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_bin; end
|
||||||
def tools_seed_tray; end
|
def tools_seed_tray; end
|
||||||
def tools_seed_trough_1; end
|
def tools_seed_trough_1; end
|
||||||
def tools_seed_trough_2; end
|
def tools_seed_trough_2; end
|
||||||
def tools_seed_trough_3; end
|
|
||||||
def tools_seeder; end
|
def tools_seeder; end
|
||||||
def tools_soil_sensor; end
|
def tools_soil_sensor; end
|
||||||
def tools_watering_nozzle; end
|
def tools_watering_nozzle; end
|
||||||
|
|
|
@ -5,7 +5,7 @@ module PointGroups
|
||||||
hash :criteria do
|
hash :criteria do
|
||||||
hash(:day) do
|
hash(:day) do
|
||||||
string :op, in: [">", "<"]
|
string :op, in: [">", "<"]
|
||||||
integer :days
|
integer :days_ago
|
||||||
end
|
end
|
||||||
hash(:string_eq) { array :*, class: String }
|
hash(:string_eq) { array :*, class: String }
|
||||||
hash(:number_eq) { array :*, class: Integer }
|
hash(:number_eq) { array :*, class: Integer }
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
module Tools
|
module Tools
|
||||||
class Destroy < Mutations::Command
|
class Destroy < Mutations::Command
|
||||||
STILL_IN_USE = "Can't delete tool because the following sequences are "\
|
STILL_IN_USE = "Can't delete tool or seed container because the " \
|
||||||
"still using it: %s"
|
"following sequences are still using it: %s"
|
||||||
STILL_IN_SLOT = "Can't delete tool because it is still in a tool slot. "\
|
STILL_IN_SLOT = "Can't delete tool or seed container because it is " \
|
||||||
"Please remove it from the tool slot first."
|
"still in a slot. Please remove it from the slot first."
|
||||||
|
|
||||||
required do
|
required do
|
||||||
model :tool, class: Tool
|
model :tool, class: Tool
|
||||||
|
@ -15,10 +15,11 @@ module Tools
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute
|
def execute
|
||||||
|
maybe_unmount_tool
|
||||||
tool.destroy!
|
tool.destroy!
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def slot
|
def slot
|
||||||
@slot ||= tool.tool_slot
|
@slot ||= tool.tool_slot
|
||||||
|
@ -33,8 +34,14 @@ private
|
||||||
end
|
end
|
||||||
|
|
||||||
def names
|
def names
|
||||||
@names ||= \
|
@names ||=
|
||||||
InUseTool.where(tool_id: tool.id).pluck(:sequence_name).join(", ")
|
InUseTool.where(tool_id: tool.id).pluck(:sequence_name).join(", ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def maybe_unmount_tool
|
||||||
|
if tool.device.mounted_tool_id == tool.id
|
||||||
|
tool.device.update!(mounted_tool_id: nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,4 +4,8 @@ class PointGroupSerializer < ApplicationSerializer
|
||||||
def point_ids
|
def point_ids
|
||||||
object.point_group_items.pluck(:point_id)
|
object.point_group_items.pluck(:point_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def criteria
|
||||||
|
object.criteria || PointGroup::DEFAULT_CRITERIA
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,9 +4,15 @@ export const panelState = (): ControlPanelState => {
|
||||||
return {
|
return {
|
||||||
homing_and_calibration: false,
|
homing_and_calibration: false,
|
||||||
motors: false,
|
motors: false,
|
||||||
encoders_and_endstops: false,
|
encoders: false,
|
||||||
|
endstops: false,
|
||||||
|
error_handling: false,
|
||||||
|
pin_bindings: false,
|
||||||
danger_zone: false,
|
danger_zone: false,
|
||||||
power_and_reset: false,
|
power_and_reset: false,
|
||||||
pin_guard: false
|
pin_guard: false,
|
||||||
|
farm_designer: false,
|
||||||
|
firmware: false,
|
||||||
|
farmbot_os: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,16 +1,10 @@
|
||||||
import { Everything } from "../../interfaces";
|
import { Everything } from "../../interfaces";
|
||||||
|
import { panelState } from "../control_panel_state";
|
||||||
|
|
||||||
export const bot: Everything["bot"] = {
|
export const bot: Everything["bot"] = {
|
||||||
"consistent": true,
|
"consistent": true,
|
||||||
"stepSize": 100,
|
"stepSize": 100,
|
||||||
"controlPanelState": {
|
"controlPanelState": panelState(),
|
||||||
"homing_and_calibration": false,
|
|
||||||
"motors": false,
|
|
||||||
"encoders_and_endstops": false,
|
|
||||||
"danger_zone": false,
|
|
||||||
"power_and_reset": false,
|
|
||||||
"pin_guard": false,
|
|
||||||
},
|
|
||||||
"hardware": {
|
"hardware": {
|
||||||
"gpio_registry": {},
|
"gpio_registry": {},
|
||||||
"mcu_params": {
|
"mcu_params": {
|
||||||
|
|
|
@ -54,5 +54,5 @@ export const fakeImages: TaggedImage[] = [
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uuid": "Image.7.5"
|
"uuid": "Image.7.5"
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {
|
||||||
} from "farmbot";
|
} from "farmbot";
|
||||||
import { fakeResource } from "../fake_resource";
|
import { fakeResource } from "../fake_resource";
|
||||||
import {
|
import {
|
||||||
ExecutableType, PinBindingType, Folder
|
ExecutableType, PinBindingType, Folder,
|
||||||
} from "farmbot/dist/resources/api_resources";
|
} from "farmbot/dist/resources/api_resources";
|
||||||
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
||||||
import { MessageType } from "../../sequences/interfaces";
|
import { MessageType } from "../../sequences/interfaces";
|
||||||
|
@ -460,7 +460,7 @@ export function fakePointGroup(): TaggedPointGroup {
|
||||||
sort_type: "xy_ascending",
|
sort_type: "xy_ascending",
|
||||||
point_ids: [],
|
point_ids: [],
|
||||||
criteria: {
|
criteria: {
|
||||||
day: { op: "<", days: 0 },
|
day: { op: "<", days_ago: 0 },
|
||||||
number_eq: {},
|
number_eq: {},
|
||||||
number_gt: {},
|
number_gt: {},
|
||||||
number_lt: {},
|
number_lt: {},
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Coordinate } from "farmbot";
|
||||||
import { VariableNameSet } from "../resources/interfaces";
|
import { VariableNameSet } from "../resources/interfaces";
|
||||||
|
|
||||||
export const fakeVariableNameSet = (
|
export const fakeVariableNameSet = (
|
||||||
label = "parent", vector = { x: 0, y: 0, z: 0 }
|
label = "parent", vector = { x: 0, y: 0, z: 0 },
|
||||||
): VariableNameSet => {
|
): VariableNameSet => {
|
||||||
const data_value: Coordinate = {
|
const data_value: Coordinate = {
|
||||||
kind: "coordinate", args: vector
|
kind: "coordinate", args: vector
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import {
|
import {
|
||||||
FarmEventWithExecutable
|
FarmEventWithExecutable,
|
||||||
} from "../farm_designer/farm_events/calendar/interfaces";
|
} from "../farm_designer/farm_events/calendar/interfaces";
|
||||||
|
|
||||||
export const TIME = {
|
export const TIME = {
|
||||||
|
@ -24,7 +24,7 @@ export const fakeFarmEventWithExecutable = (): FarmEventWithExecutable => {
|
||||||
color: "red",
|
color: "red",
|
||||||
name: "faker",
|
name: "faker",
|
||||||
kind: "sequence",
|
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",
|
"subheading": "25",
|
||||||
"id": 79,
|
"id": 79,
|
||||||
"childExecutableName": "Goto 0, 0, 0 123"
|
"childExecutableName": "Goto 0, 0, 0 123"
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -171,7 +171,7 @@ export const calendarRows = [
|
||||||
"subheading": "25",
|
"subheading": "25",
|
||||||
"id": 79,
|
"id": 79,
|
||||||
"childExecutableName": "Goto 0, 0, 0 123"
|
"childExecutableName": "Goto 0, 0, 0 123"
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -258,7 +258,7 @@ export const calendarRows = [
|
||||||
"subheading": "25",
|
"subheading": "25",
|
||||||
"id": 79,
|
"id": 79,
|
||||||
"childExecutableName": "Goto 0, 0, 0 123"
|
"childExecutableName": "Goto 0, 0, 0 123"
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -62,7 +62,7 @@ const tr0: TaggedResource = {
|
||||||
},
|
},
|
||||||
"speed": 100
|
"speed": 100
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
"args": {
|
"args": {
|
||||||
"version": 4,
|
"version": 4,
|
||||||
|
@ -287,7 +287,7 @@ const tr12: TaggedResource = {
|
||||||
"regimen_id": 11,
|
"regimen_id": 11,
|
||||||
"sequence_id": 23,
|
"sequence_id": 23,
|
||||||
"time_offset": 345900000
|
"time_offset": 345900000
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
body: [],
|
body: [],
|
||||||
},
|
},
|
||||||
|
@ -345,7 +345,7 @@ export const FAKE_RESOURCES: TaggedResource[] = [
|
||||||
tr0,
|
tr0,
|
||||||
tr14,
|
tr14,
|
||||||
tr15,
|
tr15,
|
||||||
log
|
log,
|
||||||
];
|
];
|
||||||
const KIND: keyof TaggedResource = "kind"; // Safety first, kids.
|
const KIND: keyof TaggedResource = "kind"; // Safety first, kids.
|
||||||
type ResourceGroupNumber = 0 | 1 | 2 | 3 | 4;
|
type ResourceGroupNumber = 0 | 1 | 2 | 3 | 4;
|
||||||
|
|
|
@ -9,11 +9,11 @@ import { RawApp as App, AppProps, mapStateToProps } from "../app";
|
||||||
import { mount } from "enzyme";
|
import { mount } from "enzyme";
|
||||||
import { bot } from "../__test_support__/fake_state/bot";
|
import { bot } from "../__test_support__/fake_state/bot";
|
||||||
import {
|
import {
|
||||||
fakeUser, fakeWebAppConfig, fakeFbosConfig, fakeFarmwareEnv
|
fakeUser, fakeWebAppConfig, fakeFbosConfig, fakeFarmwareEnv,
|
||||||
} from "../__test_support__/fake_state/resources";
|
} from "../__test_support__/fake_state/resources";
|
||||||
import { fakeState } from "../__test_support__/fake_state";
|
import { fakeState } from "../__test_support__/fake_state";
|
||||||
import {
|
import {
|
||||||
buildResourceIndex
|
buildResourceIndex,
|
||||||
} from "../__test_support__/resource_index_builder";
|
} from "../__test_support__/resource_index_builder";
|
||||||
import { ResourceName } from "farmbot";
|
import { ResourceName } from "farmbot";
|
||||||
import { fakeTimeSettings } from "../__test_support__/fake_time_settings";
|
import { fakeTimeSettings } from "../__test_support__/fake_time_settings";
|
||||||
|
@ -125,7 +125,7 @@ describe("<App />: NavBar", () => {
|
||||||
"Device",
|
"Device",
|
||||||
"Sequences",
|
"Sequences",
|
||||||
"Regimens",
|
"Regimens",
|
||||||
"Farmware"
|
"Farmware",
|
||||||
];
|
];
|
||||||
strings.map(string => expect(t).toContain(string));
|
strings.map(string => expect(t).toContain(string));
|
||||||
wrapper.unmount();
|
wrapper.unmount();
|
||||||
|
@ -157,7 +157,6 @@ describe("mapStateToProps()", () => {
|
||||||
const state = fakeState();
|
const state = fakeState();
|
||||||
const config = fakeFbosConfig();
|
const config = fakeFbosConfig();
|
||||||
config.body.auto_sync = true;
|
config.body.auto_sync = true;
|
||||||
config.body.api_migrated = true;
|
|
||||||
const fakeEnv = fakeFarmwareEnv();
|
const fakeEnv = fakeFarmwareEnv();
|
||||||
state.resources = buildResourceIndex([config, fakeEnv]);
|
state.resources = buildResourceIndex([config, fakeEnv]);
|
||||||
state.bot.minOsFeatureData = { api_farmware_env: "8.0.0" };
|
state.bot.minOsFeatureData = { api_farmware_env: "8.0.0" };
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
jest.mock("../util", () => {
|
jest.mock("../util", () => ({
|
||||||
return {
|
attachToRoot: jest.fn(),
|
||||||
attachToRoot: jest.fn(),
|
// Incidental mock. Can be removed if errors go away.
|
||||||
// Incidental mock. Can be removed if errors go away.
|
trim: jest.fn(x => x),
|
||||||
trim: jest.fn(x => x)
|
urlFriendly: jest.fn(),
|
||||||
};
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock("../redux/store", () => {
|
jest.mock("../redux/store", () => {
|
||||||
return { store: { dispatch: jest.fn() } };
|
return { store: { dispatch: jest.fn() } };
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock("../account/dev/dev_support", () => ({
|
jest.mock("../account/dev/dev_support", () => ({
|
||||||
DevSettings: { futureFeaturesEnabled: () => false, }
|
DevSettings: { futureFeaturesEnabled: () => false }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("../config/actions", () => {
|
jest.mock("../config/actions", () => {
|
||||||
|
|
|
@ -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");
|
||||||
|
});
|
||||||
|
});
|
|
@ -19,7 +19,7 @@ jest.mock("../session", () => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import {
|
import {
|
||||||
responseFulfilled, isLocalRequest, requestFulfilled, responseRejected
|
responseFulfilled, isLocalRequest, requestFulfilled, responseRejected,
|
||||||
} from "../interceptors";
|
} from "../interceptors";
|
||||||
import { AxiosResponse, Method } from "axios";
|
import { AxiosResponse, Method } from "axios";
|
||||||
import { uuid } from "farmbot";
|
import { uuid } from "farmbot";
|
||||||
|
|
|
@ -30,7 +30,6 @@ import "../regimens/editor/interfaces";
|
||||||
import "../regimens/interfaces";
|
import "../regimens/interfaces";
|
||||||
import "../resources/interfaces";
|
import "../resources/interfaces";
|
||||||
import "../sequences/interfaces";
|
import "../sequences/interfaces";
|
||||||
import "../tools/interfaces";
|
|
||||||
|
|
||||||
describe("interfaces", () => {
|
describe("interfaces", () => {
|
||||||
it("cant explain why coverage is 0 for interface files", () => {
|
it("cant explain why coverage is 0 for interface files", () => {
|
||||||
|
|
|
@ -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 { maybeRefreshToken } from "../refresh_token";
|
||||||
import { API } from "../api/index";
|
import { API } from "../api/index";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
buildResourceIndex,
|
buildResourceIndex,
|
||||||
FAKE_RESOURCES
|
FAKE_RESOURCES,
|
||||||
} from "../__test_support__/resource_index_builder";
|
} from "../__test_support__/resource_index_builder";
|
||||||
import { TaggedFarmEvent, SpecialStatus } from "farmbot";
|
import { TaggedFarmEvent, SpecialStatus } from "farmbot";
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ type Info = UnboundRouteConfig<{}, {}>;
|
||||||
const fakeCallback = (
|
const fakeCallback = (
|
||||||
component: ConnectedComponent,
|
component: ConnectedComponent,
|
||||||
child: ConnectedComponent | undefined,
|
child: ConnectedComponent | undefined,
|
||||||
info: Info
|
info: Info,
|
||||||
) => {
|
) => {
|
||||||
if (info.$ == "*") {
|
if (info.$ == "*") {
|
||||||
expect(component.name).toEqual("FourOhFour");
|
expect(component.name).toEqual("FourOhFour");
|
||||||
|
|
|
@ -11,7 +11,7 @@ jest.mock("axios", () => ({
|
||||||
import { API } from "../../api";
|
import { API } from "../../api";
|
||||||
import { Content } from "../../constants";
|
import { Content } from "../../constants";
|
||||||
import {
|
import {
|
||||||
requestAccountExport, generateFilename
|
requestAccountExport, generateFilename,
|
||||||
} from "../request_account_export";
|
} from "../request_account_export";
|
||||||
import { success } from "../../toast/toast";
|
import { success } from "../../toast/toast";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {
|
||||||
Widget,
|
Widget,
|
||||||
WidgetHeader,
|
WidgetHeader,
|
||||||
WidgetBody,
|
WidgetBody,
|
||||||
SaveBtn
|
SaveBtn,
|
||||||
} from "../../ui/index";
|
} from "../../ui/index";
|
||||||
import { SpecialStatus } from "farmbot";
|
import { SpecialStatus } from "farmbot";
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
|
|
|
@ -20,7 +20,7 @@ export class DangerousDeleteWidget extends
|
||||||
return <Widget>
|
return <Widget>
|
||||||
<WidgetHeader title={this.props.title} />
|
<WidgetHeader title={this.props.title} />
|
||||||
<WidgetBody>
|
<WidgetBody>
|
||||||
<div>
|
<div className={"dangerous-delete-warning-messages"}>
|
||||||
{t(this.props.warning)}
|
{t(this.props.warning)}
|
||||||
<br /><br />
|
<br /><br />
|
||||||
{t(this.props.confirmation)}
|
{t(this.props.confirmation)}
|
||||||
|
@ -42,6 +42,7 @@ export class DangerousDeleteWidget extends
|
||||||
<button
|
<button
|
||||||
onClick={this.onClick}
|
onClick={this.onClick}
|
||||||
className="red fb-button"
|
className="red fb-button"
|
||||||
|
title={t(this.props.title)}
|
||||||
type="button">
|
type="button">
|
||||||
{t(this.props.title)}
|
{t(this.props.title)}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -7,7 +7,7 @@ export function ExportAccountPanel(props: { onClick: () => void }) {
|
||||||
return <Widget>
|
return <Widget>
|
||||||
<WidgetHeader title={t("Export Account Data")} />
|
<WidgetHeader title={t("Export Account Data")} />
|
||||||
<WidgetBody>
|
<WidgetBody>
|
||||||
<div>
|
<div className={"export-account-data-description"}>
|
||||||
{t(Content.EXPORT_DATA_DESC)}
|
{t(Content.EXPORT_DATA_DESC)}
|
||||||
</div>
|
</div>
|
||||||
<form>
|
<form>
|
||||||
|
@ -19,6 +19,7 @@ export function ExportAccountPanel(props: { onClick: () => void }) {
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={4}>
|
<Col xs={4}>
|
||||||
<button className="green fb-button" type="button"
|
<button className="green fb-button" type="button"
|
||||||
|
title={t("Export")}
|
||||||
onClick={props.onClick}>
|
onClick={props.onClick}>
|
||||||
{t("Export")}
|
{t("Export")}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
BlurableInput, Widget, WidgetHeader, WidgetBody, SaveBtn
|
BlurableInput, Widget, WidgetHeader, WidgetBody, SaveBtn,
|
||||||
} from "../../ui/index";
|
} from "../../ui/index";
|
||||||
import { SettingsPropTypes } from "../interfaces";
|
import { SettingsPropTypes } from "../interfaces";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { DevMode } from "../dev_mode";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { range } from "lodash";
|
import { range } from "lodash";
|
||||||
import {
|
import {
|
||||||
setWebAppConfigValue
|
setWebAppConfigValue,
|
||||||
} from "../../../config_storage/actions";
|
} from "../../../config_storage/actions";
|
||||||
import { warning } from "../../../toast/toast";
|
import { warning } from "../../../toast/toast";
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ jest.mock("../../../config_storage/actions", () => ({
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { mount, shallow } from "enzyme";
|
import { mount, shallow } from "enzyme";
|
||||||
import {
|
import {
|
||||||
DevWidget, DevWidgetFERow, DevWidgetFBOSRow, DevWidgetDelModeRow
|
DevWidget, DevWidgetFERow, DevWidgetFBOSRow, DevWidgetDelModeRow,
|
||||||
} from "../dev_widget";
|
} from "../dev_widget";
|
||||||
import { DevSettings } from "../dev_support";
|
import { DevSettings } from "../dev_support";
|
||||||
import { setWebAppConfigValue } from "../../../config_storage/actions";
|
import { setWebAppConfigValue } from "../../../config_storage/actions";
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { store } from "../../redux/store";
|
import { store } from "../../redux/store";
|
||||||
import {
|
import {
|
||||||
getWebAppConfigValue, setWebAppConfigValue
|
getWebAppConfigValue, setWebAppConfigValue,
|
||||||
} from "../../config_storage/actions";
|
} from "../../config_storage/actions";
|
||||||
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
Widget, WidgetHeader, WidgetBody, Row, Col, BlurableInput
|
Widget, WidgetHeader, WidgetBody, Row, Col, BlurableInput,
|
||||||
} from "../../ui";
|
} from "../../ui";
|
||||||
import { ToggleButton } from "../../controls/toggle_button";
|
import { ToggleButton } from "../../controls/toggle_button";
|
||||||
import { setWebAppConfigValue } from "../../config_storage/actions";
|
import { setWebAppConfigValue } from "../../config_storage/actions";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import {
|
import {
|
||||||
Settings, ChangePassword, ExportAccountPanel, DangerousDeleteWidget
|
Settings, ChangePassword, ExportAccountPanel, DangerousDeleteWidget,
|
||||||
} from "./components";
|
} from "./components";
|
||||||
import { Props } from "./interfaces";
|
import { Props } from "./interfaces";
|
||||||
import { Page, Row, Col } from "../ui";
|
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 });
|
(key: keyof User) => (key === "email") && this.setState({ warnThem: true });
|
||||||
|
|
||||||
onChange = (e: React.FormEvent<HTMLInputElement>) => {
|
onChange = (e: React.FormEvent<HTMLInputElement>) => {
|
||||||
const { name, value } = e.currentTarget;
|
const { value } = e.currentTarget;
|
||||||
if (isKey(name)) {
|
const field = e.currentTarget.name;
|
||||||
this.tempHack(name);
|
if (isKey(field)) {
|
||||||
this.props.dispatch(edit(this.props.user, { [name]: value }));
|
this.tempHack(field);
|
||||||
|
this.props.dispatch(edit(this.props.user, { [field]: value }));
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Bad key: " + name);
|
throw new Error("Bad key: " + field);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ const mockFeatures = [
|
||||||
storageKey: "weedDetector",
|
storageKey: "weedDetector",
|
||||||
callback: jest.fn(),
|
callback: jest.fn(),
|
||||||
value: false
|
value: false
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const mocks = {
|
const mocks = {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { BooleanSetting } from "../../session_keys";
|
import { BooleanSetting } from "../../session_keys";
|
||||||
import { Content } from "../../constants";
|
import { Content } from "../../constants";
|
||||||
import {
|
import {
|
||||||
GetWebAppConfigValue, setWebAppConfigValue
|
GetWebAppConfigValue, setWebAppConfigValue,
|
||||||
} from "../../config_storage/actions";
|
} from "../../config_storage/actions";
|
||||||
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
|
@ -78,7 +78,7 @@ export const fetchLabFeatures =
|
||||||
storageKey: BooleanSetting.user_interface_read_only_mode,
|
storageKey: BooleanSetting.user_interface_read_only_mode,
|
||||||
value: false,
|
value: false,
|
||||||
displayInvert: false,
|
displayInvert: false,
|
||||||
}
|
},
|
||||||
].map(fetchSettingValue(getConfigValue)));
|
].map(fetchSettingValue(getConfigValue)));
|
||||||
|
|
||||||
/** Always allow toggling from true => false (deactivate).
|
/** Always allow toggling from true => false (deactivate).
|
||||||
|
|
|
@ -11,7 +11,7 @@ interface LabsFeaturesListProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LabsFeaturesList(props: LabsFeaturesListProps) {
|
export function LabsFeaturesList(props: LabsFeaturesListProps) {
|
||||||
return <div>
|
return <div className="labs-features-list">
|
||||||
{fetchLabFeatures(props.getConfigValue).map((feature, i) => {
|
{fetchLabFeatures(props.getConfigValue).map((feature, i) => {
|
||||||
const displayValue = feature.displayInvert ? !feature.value : feature.value;
|
const displayValue = feature.displayInvert ? !feature.value : feature.value;
|
||||||
return <Row key={i}>
|
return <Row key={i}>
|
||||||
|
@ -23,6 +23,7 @@ export function LabsFeaturesList(props: LabsFeaturesListProps) {
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={2}>
|
<Col xs={2}>
|
||||||
<ToggleButton
|
<ToggleButton
|
||||||
|
title={t("toggle feature")}
|
||||||
toggleValue={displayValue ? 1 : 0}
|
toggleValue={displayValue ? 1 : 0}
|
||||||
toggleAction={() => props.onToggle(feature)
|
toggleAction={() => props.onToggle(feature)
|
||||||
.then(() => feature.callback && feature.callback())}
|
.then(() => feature.callback && feature.callback())}
|
||||||
|
|
|
@ -9,9 +9,8 @@ interface DataDumpExport { device?: DeviceAccountSettings; }
|
||||||
type Response = AxiosResponse<DataDumpExport | undefined>;
|
type Response = AxiosResponse<DataDumpExport | undefined>;
|
||||||
|
|
||||||
export function generateFilename({ device }: DataDumpExport): string {
|
export function generateFilename({ device }: DataDumpExport): string {
|
||||||
let name: string;
|
const nameAndId = device ? (device.name + "_" + device.id) : "farmbot";
|
||||||
name = device ? (device.name + "_" + device.id) : "farmbot";
|
return `export_${nameAndId}.json`.toLowerCase();
|
||||||
return `export_${name}.json`.toLowerCase();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thanks, @KOL - https://stackoverflow.com/a/19328891/1064917
|
// Thanks, @KOL - https://stackoverflow.com/a/19328891/1064917
|
||||||
|
|
|
@ -158,6 +158,10 @@ export class API {
|
||||||
get farmwareInstallationPath() {
|
get farmwareInstallationPath() {
|
||||||
return `${this.baseUrl}/api/farmware_installations/`;
|
return `${this.baseUrl}/api/farmware_installations/`;
|
||||||
}
|
}
|
||||||
|
/** /api/first_party_farmwares */
|
||||||
|
get firstPartyFarmwarePath() {
|
||||||
|
return `${this.baseUrl}/api/first_party_farmwares`;
|
||||||
|
}
|
||||||
/** /api/alerts/:id */
|
/** /api/alerts/:id */
|
||||||
get alertPath() { return `${this.baseUrl}/api/alerts/`; }
|
get alertPath() { return `${this.baseUrl}/api/alerts/`; }
|
||||||
/** /api/global_bulletins/:id */
|
/** /api/global_bulletins/:id */
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Session } from "./session";
|
import { Session } from "./session";
|
||||||
|
import { ExternalUrl } from "./external_urls";
|
||||||
|
|
||||||
const OUTER_STYLE: React.CSSProperties = {
|
const OUTER_STYLE: React.CSSProperties = {
|
||||||
borderRadius: "10px",
|
borderRadius: "10px",
|
||||||
|
@ -47,7 +48,7 @@ export function Apology(_: {}) {
|
||||||
<li>
|
<li>
|
||||||
<span>
|
<span>
|
||||||
Send a report to our developer team via the
|
Send a report to our developer team via the
|
||||||
<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
|
forum</a>. Including additional information (such as steps leading up
|
||||||
to the error) helps us identify solutions more quickly.
|
to the error) helps us identify solutions more quickly.
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { validBotLocationData, validFwConfig, validFbosConfig } from "./util";
|
||||||
import { BooleanSetting } from "./session_keys";
|
import { BooleanSetting } from "./session_keys";
|
||||||
import { getPathArray } from "./history";
|
import { getPathArray } from "./history";
|
||||||
import {
|
import {
|
||||||
getWebAppConfigValue, GetWebAppConfigValue
|
getWebAppConfigValue, GetWebAppConfigValue,
|
||||||
} from "./config_storage/actions";
|
} from "./config_storage/actions";
|
||||||
import { takeSortedLogs } from "./logs/state_to_props";
|
import { takeSortedLogs } from "./logs/state_to_props";
|
||||||
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
|
||||||
|
@ -99,7 +99,7 @@ const MUST_LOAD: ResourceName[] = [
|
||||||
"FarmEvent",
|
"FarmEvent",
|
||||||
"Point",
|
"Point",
|
||||||
"Device",
|
"Device",
|
||||||
"Tool" // Sequence editor needs this for rendering.
|
"Tool", // Sequence editor needs this for rendering.
|
||||||
];
|
];
|
||||||
|
|
||||||
export class RawApp extends React.Component<AppProps, {}> {
|
export class RawApp extends React.Component<AppProps, {}> {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {
|
import {
|
||||||
fetchReleases, fetchMinOsFeatureData, FEATURE_MIN_VERSIONS_URL,
|
fetchReleases, fetchMinOsFeatureData,
|
||||||
fetchLatestGHBetaRelease
|
fetchLatestGHBetaRelease,
|
||||||
} from "../devices/actions";
|
} from "../devices/actions";
|
||||||
import { AuthState } from "./interfaces";
|
import { AuthState } from "./interfaces";
|
||||||
import { ReduxAction } from "../redux/interfaces";
|
import { ReduxAction } from "../redux/interfaces";
|
||||||
|
@ -10,12 +10,13 @@ import { API } from "../api";
|
||||||
import {
|
import {
|
||||||
responseFulfilled,
|
responseFulfilled,
|
||||||
responseRejected,
|
responseRejected,
|
||||||
requestFulfilled
|
requestFulfilled,
|
||||||
} from "../interceptors";
|
} from "../interceptors";
|
||||||
import { Actions } from "../constants";
|
import { Actions } from "../constants";
|
||||||
import { connectDevice } from "../connectivity/connect_device";
|
import { connectDevice } from "../connectivity/connect_device";
|
||||||
import { getFirstPartyFarmwareList } from "../farmware/actions";
|
import { getFirstPartyFarmwareList } from "../farmware/actions";
|
||||||
import { readOnlyInterceptor } from "../read_only_mode";
|
import { readOnlyInterceptor } from "../read_only_mode";
|
||||||
|
import { ExternalUrl } from "../external_urls";
|
||||||
|
|
||||||
export function didLogin(authState: AuthState, dispatch: Function) {
|
export function didLogin(authState: AuthState, dispatch: Function) {
|
||||||
API.setBaseUrl(authState.token.unencoded.iss);
|
API.setBaseUrl(authState.token.unencoded.iss);
|
||||||
|
@ -24,7 +25,7 @@ export function didLogin(authState: AuthState, dispatch: Function) {
|
||||||
beta_os_update_server && beta_os_update_server != "NOT_SET" &&
|
beta_os_update_server && beta_os_update_server != "NOT_SET" &&
|
||||||
dispatch(fetchLatestGHBetaRelease(beta_os_update_server));
|
dispatch(fetchLatestGHBetaRelease(beta_os_update_server));
|
||||||
dispatch(getFirstPartyFarmwareList());
|
dispatch(getFirstPartyFarmwareList());
|
||||||
dispatch(fetchMinOsFeatureData(FEATURE_MIN_VERSIONS_URL));
|
dispatch(fetchMinOsFeatureData(ExternalUrl.featureMinVersions));
|
||||||
dispatch(setToken(authState));
|
dispatch(setToken(authState));
|
||||||
Sync.fetchSyncData(dispatch);
|
Sync.fetchSyncData(dispatch);
|
||||||
dispatch(connectDevice(authState));
|
dispatch(connectDevice(authState));
|
||||||
|
|
|
@ -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", () => ({
|
jest.mock("../../session", () => ({
|
||||||
Session: {
|
Session: {
|
||||||
fetchStoredToken: jest.fn(),
|
fetchStoredToken: jest.fn(),
|
||||||
getAll: () => undefined,
|
getAll: () => undefined,
|
||||||
clear: jest.fn()
|
clear: jest.fn(),
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock("../../auth/actions", () => ({
|
jest.mock("../../auth/actions", () => ({
|
||||||
didLogin: jest.fn(),
|
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 { ready, storeToken } from "../actions";
|
||||||
import { setToken, didLogin } from "../../auth/actions";
|
import { setToken, didLogin } from "../../auth/actions";
|
||||||
import { Session } from "../../session";
|
import { Session } from "../../session";
|
||||||
import { auth } from "../../__test_support__/fake_state/token";
|
import { auth } from "../../__test_support__/fake_state/token";
|
||||||
import { fakeState } from "../../__test_support__/fake_state";
|
import { fakeState } from "../../__test_support__/fake_state";
|
||||||
|
|
||||||
describe("Actions", () => {
|
describe("ready()", () => {
|
||||||
it("calls didLogin()", () => {
|
it("uses new token", async () => {
|
||||||
jest.resetAllMocks();
|
const fakeAuth = { token: "fake token data" };
|
||||||
|
mockTimeout = Promise.resolve(fakeAuth);
|
||||||
const dispatch = jest.fn();
|
const dispatch = jest.fn();
|
||||||
const thunk = ready();
|
const thunk = ready();
|
||||||
thunk(dispatch, fakeState);
|
const state = fakeState();
|
||||||
expect(setToken).toHaveBeenCalled();
|
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", () => {
|
it("uses old token", async () => {
|
||||||
jest.resetAllMocks();
|
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 dispatch = jest.fn();
|
||||||
const state = fakeState();
|
const state = fakeState();
|
||||||
delete state.auth;
|
delete state.auth;
|
||||||
const getState = () => state;
|
const getState = () => state;
|
||||||
const thunk = ready();
|
const thunk = ready();
|
||||||
|
console.warn = jest.fn();
|
||||||
thunk(dispatch, getState);
|
thunk(dispatch, getState);
|
||||||
|
expect(setToken).not.toHaveBeenCalled();
|
||||||
|
expect(didLogin).not.toHaveBeenCalled();
|
||||||
|
expect(console.warn).not.toHaveBeenCalled();
|
||||||
expect(Session.clear).toHaveBeenCalled();
|
expect(Session.clear).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("storeToken()", () => {
|
||||||
it("stores token", () => {
|
it("stores token", () => {
|
||||||
const old = auth;
|
const old = auth;
|
||||||
old.token.unencoded.jti = "old";
|
old.token.unencoded.jti = "old";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
toggleWebAppBool, getWebAppConfigValue, setWebAppConfigValue
|
toggleWebAppBool, getWebAppConfigValue, setWebAppConfigValue,
|
||||||
} from "../actions";
|
} from "../actions";
|
||||||
import { BooleanSetting, NumericSetting } from "../../session_keys";
|
import { BooleanSetting, NumericSetting } from "../../session_keys";
|
||||||
import { edit, save } from "../../api/crud";
|
import { edit, save } from "../../api/crud";
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
BooleanConfigKey,
|
BooleanConfigKey,
|
||||||
WebAppConfig,
|
WebAppConfig,
|
||||||
NumberConfigKey,
|
NumberConfigKey,
|
||||||
StringConfigKey
|
StringConfigKey,
|
||||||
} from "farmbot/dist/resources/configs/web_app";
|
} from "farmbot/dist/resources/configs/web_app";
|
||||||
import { getWebAppConfig } from "../resources/getters";
|
import { getWebAppConfig } from "../resources/getters";
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { fakeState } from "../../__test_support__/fake_state";
|
||||||
import { GetState } from "../../redux/interfaces";
|
import { GetState } from "../../redux/interfaces";
|
||||||
import { handleInbound } from "../auto_sync_handle_inbound";
|
import { handleInbound } from "../auto_sync_handle_inbound";
|
||||||
import {
|
import {
|
||||||
handleCreateOrUpdate
|
handleCreateOrUpdate,
|
||||||
} from "../auto_sync";
|
} from "../auto_sync";
|
||||||
import { destroyOK } from "../../resources/actions";
|
import { destroyOK } from "../../resources/actions";
|
||||||
import { SkipMqttData, BadMqttData, UpdateMqttData, DeleteMqttData } from "../interfaces";
|
import { SkipMqttData, BadMqttData, UpdateMqttData, DeleteMqttData } from "../interfaces";
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
asTaggedResource,
|
asTaggedResource,
|
||||||
handleCreate,
|
handleCreate,
|
||||||
handleUpdate,
|
handleUpdate,
|
||||||
handleCreateOrUpdate
|
handleCreateOrUpdate,
|
||||||
} from "../auto_sync";
|
} from "../auto_sync";
|
||||||
import { SpecialStatus, TaggedSequence } from "farmbot";
|
import { SpecialStatus, TaggedSequence } from "farmbot";
|
||||||
import { Actions } from "../../constants";
|
import { Actions } from "../../constants";
|
||||||
|
|
|
@ -35,7 +35,7 @@ describe("attachEventListeners", () => {
|
||||||
].map(e => expect(dev.on).toHaveBeenCalledWith(e, expect.any(Function)));
|
].map(e => expect(dev.on).toHaveBeenCalledWith(e, expect.any(Function)));
|
||||||
[
|
[
|
||||||
"message",
|
"message",
|
||||||
"reconnect"
|
"reconnect",
|
||||||
].map(e => {
|
].map(e => {
|
||||||
if (dev.client) {
|
if (dev.client) {
|
||||||
expect(dev.client.on).toHaveBeenCalledWith(e, expect.any(Function));
|
expect(dev.client.on).toHaveBeenCalledWith(e, expect.any(Function));
|
||||||
|
|
|
@ -1,21 +1,15 @@
|
||||||
jest.mock("../../slow_down", () => {
|
jest.mock("../../slow_down", () => ({
|
||||||
return {
|
slowDown: jest.fn((fn: Function) => fn)
|
||||||
slowDown: jest.fn((fn: Function) => fn),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock("../../../devices/actions", () => ({
|
|
||||||
badVersion: jest.fn(),
|
|
||||||
EXPECTED_MAJOR: 1,
|
|
||||||
EXPECTED_MINOR: 0,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock("../../../devices/actions", () => ({ badVersion: jest.fn() }));
|
||||||
|
|
||||||
import {
|
import {
|
||||||
onStatus,
|
onStatus,
|
||||||
incomingStatus,
|
incomingStatus,
|
||||||
incomingLegacyStatus,
|
incomingLegacyStatus,
|
||||||
onLegacyStatus,
|
onLegacyStatus,
|
||||||
HACKY_FLAGS
|
HACKY_FLAGS,
|
||||||
} from "../../connect_device";
|
} from "../../connect_device";
|
||||||
import { slowDown } from "../../slow_down";
|
import { slowDown } from "../../slow_down";
|
||||||
import { fakeState } from "../../../__test_support__/fake_state";
|
import { fakeState } from "../../../__test_support__/fake_state";
|
||||||
|
@ -49,8 +43,10 @@ describe("onStatus()", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("version ok", () => {
|
it("version ok", () => {
|
||||||
|
globalConfig.MINIMUM_FBOS_VERSION = "1.0.0";
|
||||||
callOnStatus("1.0.0");
|
callOnStatus("1.0.0");
|
||||||
expect(badVersion).not.toHaveBeenCalled();
|
expect(badVersion).not.toHaveBeenCalled();
|
||||||
|
delete globalConfig.MINIMUM_FBOS_VERSION;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { getDevice } from "../../device";
|
||||||
import { store } from "../../redux/store";
|
import { store } from "../../redux/store";
|
||||||
import { Actions } from "../../constants";
|
import { Actions } from "../../constants";
|
||||||
import {
|
import {
|
||||||
startTracking, outstandingRequests, stopTracking, cleanUUID
|
startTracking, outstandingRequests, stopTracking, cleanUUID,
|
||||||
} from "../data_consistency";
|
} from "../data_consistency";
|
||||||
|
|
||||||
const unprocessedUuid = "~UU.ID~";
|
const unprocessedUuid = "~UU.ID~";
|
||||||
|
|
|
@ -8,7 +8,7 @@ jest.mock("../index", () => ({
|
||||||
import {
|
import {
|
||||||
readPing,
|
readPing,
|
||||||
startPinging,
|
startPinging,
|
||||||
PING_INTERVAL
|
PING_INTERVAL,
|
||||||
} from "../ping_mqtt";
|
} from "../ping_mqtt";
|
||||||
import { Farmbot, RpcRequest, RpcRequestBodyItem } from "farmbot";
|
import { Farmbot, RpcRequest, RpcRequestBodyItem } from "farmbot";
|
||||||
import { FarmBotInternalConfig } from "farmbot/dist/config";
|
import { FarmBotInternalConfig } from "farmbot/dist/config";
|
||||||
|
|
|
@ -41,7 +41,7 @@ describe("connectivity reducer", () => {
|
||||||
it("broadcasts PING_OK", () => {
|
it("broadcasts PING_OK", () => {
|
||||||
pingOK("yep", 123);
|
pingOK("yep", 123);
|
||||||
expect(store.dispatch).toHaveBeenCalledWith({
|
expect(store.dispatch).toHaveBeenCalledWith({
|
||||||
payload: { at: 123, id: "yep", },
|
payload: { at: 123, id: "yep" },
|
||||||
type: "PING_OK",
|
type: "PING_OK",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { TaggedResource, SpecialStatus } from "farmbot";
|
||||||
import { overwrite, init } from "../api/crud";
|
import { overwrite, init } from "../api/crud";
|
||||||
import { handleInbound } from "./auto_sync_handle_inbound";
|
import { handleInbound } from "./auto_sync_handle_inbound";
|
||||||
import {
|
import {
|
||||||
SyncPayload, MqttDataResult, Reason, UpdateMqttData
|
SyncPayload, MqttDataResult, Reason, UpdateMqttData,
|
||||||
} from "./interfaces";
|
} from "./interfaces";
|
||||||
import { outstandingRequests } from "./data_consistency";
|
import { outstandingRequests } from "./data_consistency";
|
||||||
import { newTaggedResource } from "../sync/actions";
|
import { newTaggedResource } from "../sync/actions";
|
||||||
|
|
|
@ -8,13 +8,7 @@ import { success, error, info, warning, fun, busy } from "../toast/toast";
|
||||||
import { HardwareState } from "../devices/interfaces";
|
import { HardwareState } from "../devices/interfaces";
|
||||||
import { GetState, ReduxAction } from "../redux/interfaces";
|
import { GetState, ReduxAction } from "../redux/interfaces";
|
||||||
import { Content, Actions } from "../constants";
|
import { Content, Actions } from "../constants";
|
||||||
import {
|
import { commandOK, badVersion, commandErr } from "../devices/actions";
|
||||||
EXPECTED_MAJOR,
|
|
||||||
EXPECTED_MINOR,
|
|
||||||
commandOK,
|
|
||||||
badVersion,
|
|
||||||
commandErr
|
|
||||||
} from "../devices/actions";
|
|
||||||
import { init } from "../api/crud";
|
import { init } from "../api/crud";
|
||||||
import { AuthState } from "../auth/interfaces";
|
import { AuthState } from "../auth/interfaces";
|
||||||
import { autoSync } from "./auto_sync";
|
import { autoSync } from "./auto_sync";
|
||||||
|
@ -123,7 +117,7 @@ const setBothUp = () => bothUp();
|
||||||
const legacyChecks = (getState: GetState) => {
|
const legacyChecks = (getState: GetState) => {
|
||||||
const { controller_version } = getState().bot.hardware.informational_settings;
|
const { controller_version } = getState().bot.hardware.informational_settings;
|
||||||
if (HACKY_FLAGS.needVersionCheck && controller_version) {
|
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(); }
|
if (!IS_OK) { badVersion(); }
|
||||||
HACKY_FLAGS.needVersionCheck = false;
|
HACKY_FLAGS.needVersionCheck = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {
|
||||||
actOnChannelName,
|
actOnChannelName,
|
||||||
showLogOnScreen,
|
showLogOnScreen,
|
||||||
speakLogAloud,
|
speakLogAloud,
|
||||||
initLog
|
initLog,
|
||||||
} from "./connect_device";
|
} from "./connect_device";
|
||||||
import { GetState } from "../redux/interfaces";
|
import { GetState } from "../redux/interfaces";
|
||||||
import { Log } from "farmbot/dist/resources/api_resources";
|
import { Log } from "farmbot/dist/resources/api_resources";
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
dispatchNetworkUp,
|
dispatchNetworkUp,
|
||||||
dispatchQosStart,
|
dispatchQosStart,
|
||||||
pingOK,
|
pingOK,
|
||||||
pingNO
|
pingNO,
|
||||||
} from "./index";
|
} from "./index";
|
||||||
import { isNumber } from "lodash";
|
import { isNumber } from "lodash";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { generateReducer } from "../redux/generate_reducer";
|
||||||
import { Actions } from "../constants";
|
import { Actions } from "../constants";
|
||||||
import {
|
import {
|
||||||
ConnectionState,
|
ConnectionState,
|
||||||
EdgeStatus
|
EdgeStatus,
|
||||||
} from "./interfaces";
|
} from "./interfaces";
|
||||||
import { startPing, completePing, failPing } from "../devices/connectivity/qos";
|
import { startPing, completePing, failPing } from "../devices/connectivity/qos";
|
||||||
|
|
||||||
|
|
|
@ -39,8 +39,8 @@ export namespace ToolTips {
|
||||||
few sequences to verify that everything works as expected.`);
|
few sequences to verify that everything works as expected.`);
|
||||||
|
|
||||||
export const PIN_BINDINGS =
|
export const PIN_BINDINGS =
|
||||||
trim(`Assign a sequence to execute when a Raspberry Pi GPIO pin is
|
trim(`Assign an action or sequence to execute when a Raspberry Pi
|
||||||
activated.`);
|
GPIO pin is activated.`);
|
||||||
|
|
||||||
export const PIN_BINDING_WARNING =
|
export const PIN_BINDING_WARNING =
|
||||||
trim(`Warning: Binding to a pin without a physical button and
|
trim(`Warning: Binding to a pin without a physical button and
|
||||||
|
@ -51,24 +51,38 @@ export namespace ToolTips {
|
||||||
trim(`Diagnose connectivity issues with FarmBot and the browser.`);
|
trim(`Diagnose connectivity issues with FarmBot and the browser.`);
|
||||||
|
|
||||||
// Hardware Settings: Homing and Calibration
|
// Hardware Settings: Homing and Calibration
|
||||||
export const HOMING =
|
export const HOMING_ENCODERS =
|
||||||
trim(`If encoders or end-stops are enabled, home axis (find zero).`);
|
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
|
trim(`If encoders or end-stops are enabled, home axis and determine
|
||||||
maximum.`);
|
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 =
|
export const SET_ZERO_POSITION =
|
||||||
trim(`Set the current location as zero.`);
|
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
|
trim(`If encoders or end-stops are enabled, find the home position
|
||||||
when the device powers on.
|
when the device powers on. Warning! This will perform homing on all
|
||||||
Warning! This will perform homing on all axes when the
|
axes when the device powers on. Encoders or endstops must be enabled.
|
||||||
device powers on. Encoders or endstops must be enabled.
|
|
||||||
It is recommended to make sure homing works properly before enabling
|
It is recommended to make sure homing works properly before enabling
|
||||||
this feature. (default: disabled)`);
|
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 =
|
export const STOP_AT_HOME =
|
||||||
trim(`Stop at the home location of the axis. (default: disabled)`);
|
trim(`Stop at the home location of the axis. (default: disabled)`);
|
||||||
|
|
||||||
|
@ -85,18 +99,7 @@ export namespace ToolTips {
|
||||||
trim(`Set the length of each axis to provide software limits.
|
trim(`Set the length of each axis to provide software limits.
|
||||||
Used only if STOP AT MAX is enabled. (default: 0 (disabled))`);
|
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
|
// 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 =
|
export const MAX_SPEED =
|
||||||
trim(`Maximum travel speed after acceleration in millimeters per second.
|
trim(`Maximum travel speed after acceleration in millimeters per second.
|
||||||
(default: x: 80mm/s, y: 80mm/s, z: 16mm/s)`);
|
(default: x: 80mm/s, y: 80mm/s, z: 16mm/s)`);
|
||||||
|
@ -132,18 +135,22 @@ export namespace ToolTips {
|
||||||
export const MOTOR_CURRENT =
|
export const MOTOR_CURRENT =
|
||||||
trim(`Motor current in milliamps. (default: 600)`);
|
trim(`Motor current in milliamps. (default: 600)`);
|
||||||
|
|
||||||
export const STALL_SENSITIVITY =
|
|
||||||
trim(`Motor stall sensitivity. (default: 30)`);
|
|
||||||
|
|
||||||
export const ENABLE_X2_MOTOR =
|
export const ENABLE_X2_MOTOR =
|
||||||
trim(`Enable use of a second x-axis motor. Connects to E0 on RAMPS.
|
trim(`Enable use of a second x-axis motor. Connects to E0 on RAMPS.
|
||||||
(default: enabled)`);
|
(default: enabled)`);
|
||||||
|
|
||||||
// Hardware Settings: Encoders and Endstops
|
// Hardware Settings: Encoders / Stall Detection
|
||||||
export const ENABLE_ENCODERS =
|
export const ENABLE_ENCODERS =
|
||||||
trim(`Enable use of rotary encoders for stall detection,
|
trim(`Enable use of rotary encoders for stall detection,
|
||||||
calibration and homing. (default: enabled)`);
|
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 =
|
export const ENCODER_POSITIONING =
|
||||||
trim(`Use encoders for positioning. (default: disabled)`);
|
trim(`Use encoders for positioning. (default: disabled)`);
|
||||||
|
|
||||||
|
@ -151,17 +158,22 @@ export namespace ToolTips {
|
||||||
trim(`Reverse the direction of encoder position reading.
|
trim(`Reverse the direction of encoder position reading.
|
||||||
(default: disabled)`);
|
(default: disabled)`);
|
||||||
|
|
||||||
export const MAX_MISSED_STEPS =
|
export const MAX_MISSED_STEPS_ENCODERS =
|
||||||
trim(`Number of steps missed (determined by encoder) before motor is
|
trim(`Number of steps missed (determined by encoder) before motor is
|
||||||
considered to have stalled. (default: 5)`);
|
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)`);
|
trim(`Reduction to missed step total for every good step. (default: 5)`);
|
||||||
|
|
||||||
export const ENCODER_SCALING =
|
export const ENCODER_SCALING =
|
||||||
trim(`encoder scaling factor = 10000 * (motor resolution * microsteps)
|
trim(`encoder scaling factor = 10000 * (motor resolution * microsteps)
|
||||||
/ (encoder resolution). (default: 5556 (10000*200/360))`);
|
/ (encoder resolution). (default: 5556 (10000*200/360))`);
|
||||||
|
|
||||||
|
// Hardware Settings: Endstops
|
||||||
export const ENABLE_ENDSTOPS =
|
export const ENABLE_ENDSTOPS =
|
||||||
trim(`Enable use of electronic end-stops for end detection,
|
trim(`Enable use of electronic end-stops for end detection,
|
||||||
calibration and homing. (default: disabled)`);
|
calibration and homing. (default: disabled)`);
|
||||||
|
@ -173,6 +185,18 @@ export namespace ToolTips {
|
||||||
trim(`Invert axis end-stops. Enable for normally closed (NC),
|
trim(`Invert axis end-stops. Enable for normally closed (NC),
|
||||||
disable for normally open (NO). (default: disabled)`);
|
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
|
// Hardware Settings: Pin Guard
|
||||||
export const PIN_GUARD_PIN_NUMBER =
|
export const PIN_GUARD_PIN_NUMBER =
|
||||||
trim(`The number of the pin to guard. This pin will be set to the specified
|
trim(`The number of the pin to guard. This pin will be set to the specified
|
||||||
|
@ -263,8 +287,12 @@ export namespace ToolTips {
|
||||||
|
|
||||||
export const FIND_HOME =
|
export const FIND_HOME =
|
||||||
trim(`The Find Home step instructs the device to perform a homing
|
trim(`The Find Home step instructs the device to perform a homing
|
||||||
command (using encoders or endstops) to find and set zero for
|
command (using encoders, stall detection, or endstops) to find and set
|
||||||
the chosen axis or axes.`);
|
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 =
|
export const IF =
|
||||||
trim(`Execute a sequence if a condition is satisfied. If the condition
|
trim(`Execute a sequence if a condition is satisfied. If the condition
|
||||||
|
@ -620,8 +648,8 @@ export namespace Content {
|
||||||
trim(`Restart the Farmduino or Arduino firmware.`);
|
trim(`Restart the Farmduino or Arduino firmware.`);
|
||||||
|
|
||||||
export const OS_AUTO_UPDATE =
|
export const OS_AUTO_UPDATE =
|
||||||
trim(`When enabled, FarmBot OS will periodically check for, download,
|
trim(`When enabled, FarmBot OS will automatically download and install
|
||||||
and install updates automatically.`);
|
software updates at the chosen time.`);
|
||||||
|
|
||||||
export const AUTO_SYNC =
|
export const AUTO_SYNC =
|
||||||
trim(`When enabled, device resources such as sequences and regimens
|
trim(`When enabled, device resources such as sequences and regimens
|
||||||
|
@ -635,7 +663,7 @@ export namespace Content {
|
||||||
back on, unplug FarmBot and plug it back in.`);
|
back on, unplug FarmBot and plug it back in.`);
|
||||||
|
|
||||||
export const OS_BETA_RELEASES =
|
export const OS_BETA_RELEASES =
|
||||||
trim(`Warning! Opting in to FarmBot OS beta releases may reduce
|
trim(`Warning! Leaving the stable FarmBot OS release channel may reduce
|
||||||
FarmBot system stability. Are you sure?`);
|
FarmBot system stability. Are you sure?`);
|
||||||
|
|
||||||
export const DIAGNOSTIC_CHECK =
|
export const DIAGNOSTIC_CHECK =
|
||||||
|
@ -674,9 +702,9 @@ export namespace Content {
|
||||||
trim(`FarmBot sent a malformed message. You may need to upgrade
|
trim(`FarmBot sent a malformed message. You may need to upgrade
|
||||||
FarmBot OS. Please upgrade FarmBot OS and log back in.`);
|
FarmBot OS. Please upgrade FarmBot OS and log back in.`);
|
||||||
|
|
||||||
export const OLD_FBOS_REC_UPGRADE = trim(`Your version of FarmBot OS is
|
export const OLD_FBOS_REC_UPGRADE =
|
||||||
outdated and will soon no longer be supported. Please update your device as
|
trim(`Your version of FarmBot OS is outdated and will soon no longer
|
||||||
soon as possible.`);
|
be supported. Please update your device as soon as possible.`);
|
||||||
|
|
||||||
export const EXPERIMENTAL_WARNING =
|
export const EXPERIMENTAL_WARNING =
|
||||||
trim(`Warning! This is an EXPERIMENTAL feature. This feature may be
|
trim(`Warning! This is an EXPERIMENTAL feature. This feature may be
|
||||||
|
@ -715,8 +743,8 @@ export namespace Content {
|
||||||
|
|
||||||
export const END_DETECTION_DISABLED =
|
export const END_DETECTION_DISABLED =
|
||||||
trim(`This command will not execute correctly because you do not have
|
trim(`This command will not execute correctly because you do not have
|
||||||
encoders or endstops enabled for the chosen axis. Enable endstops or
|
encoders, stall detection, or endstops enabled for the chosen axis.
|
||||||
encoders from the Device page for: `);
|
Enable endstops, encoders, or stall detection from the Device page for: `);
|
||||||
|
|
||||||
export const IN_USE =
|
export const IN_USE =
|
||||||
trim(`Used in another resource. Protected from deletion.`);
|
trim(`Used in another resource. Protected from deletion.`);
|
||||||
|
@ -784,7 +812,10 @@ export namespace Content {
|
||||||
trim(`add this crop on OpenFarm?`);
|
trim(`add this crop on OpenFarm?`);
|
||||||
|
|
||||||
export const NO_TOOLS =
|
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 =
|
export const MOUNTED_TOOL =
|
||||||
trim(`The tool currently mounted to the UTM can be set here or by using
|
trim(`The tool currently mounted to the UTM can be set here or by using
|
||||||
|
@ -859,12 +890,23 @@ export namespace TourContent {
|
||||||
selecting one, and dragging it into the garden.`);
|
selecting one, and dragging it into the garden.`);
|
||||||
|
|
||||||
export const ADD_TOOLS =
|
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 =
|
export const ADD_TOOLS_SLOTS =
|
||||||
trim(`Add the newly created tools and seed containers to the
|
trim(`Add the newly created tools and seed containers to the
|
||||||
corresponding tool slots on FarmBot:
|
corresponding slots on FarmBot:
|
||||||
press edit and then + to create a tool slot.`);
|
press the + button to create a slot.`);
|
||||||
|
|
||||||
export const ADD_PERIPHERALS =
|
export const ADD_PERIPHERALS =
|
||||||
trim(`Press edit and then the + button to add peripherals.`);
|
trim(`Press edit and then the + button to add peripherals.`);
|
||||||
|
@ -902,6 +944,103 @@ export namespace TourContent {
|
||||||
trim(`Toggle various settings to customize your web app experience.`);
|
trim(`Toggle various settings to customize your web app experience.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum DeviceSetting {
|
||||||
|
// 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`,
|
||||||
|
|
||||||
|
// Danger Zone
|
||||||
|
dangerZone = `Danger Zone`,
|
||||||
|
resetHardwareParams = `Reset hardware parameter defaults`,
|
||||||
|
|
||||||
|
// Pin Bindings
|
||||||
|
pinBindings = `Pin Bindings`,
|
||||||
|
|
||||||
|
// 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`,
|
||||||
|
restartFirmware = `Restart Firmware`,
|
||||||
|
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`,
|
||||||
|
flashFirmware = `Flash firmware`,
|
||||||
|
}
|
||||||
|
|
||||||
export namespace DiagnosticMessages {
|
export namespace DiagnosticMessages {
|
||||||
export const OK = trim(`All systems nominal.`);
|
export const OK = trim(`All systems nominal.`);
|
||||||
|
|
||||||
|
@ -924,8 +1063,7 @@ export namespace DiagnosticMessages {
|
||||||
but we have no recent record of FarmBot connecting to the internet.
|
but we have no recent record of FarmBot connecting to the internet.
|
||||||
This usually happens because of poor WiFi connectivity in the garden,
|
This usually happens because of poor WiFi connectivity in the garden,
|
||||||
a bad password during configuration, a very long power outage, or
|
a bad password during configuration, a very long power outage, or
|
||||||
blocked ports on FarmBot's local network. Please refer IT staff to
|
blocked ports on FarmBot's local network. Please refer IT staff to:`);
|
||||||
https://software.farm.bot/docs/for-it-security-professionals`);
|
|
||||||
|
|
||||||
export const NO_WS_AVAILABLE = trim(`You are either offline, using a web
|
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
|
browser that does not support WebSockets, or are behind a firewall that
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { mount } from "enzyme";
|
||||||
import { RawControls as Controls } from "../controls";
|
import { RawControls as Controls } from "../controls";
|
||||||
import { bot } from "../../__test_support__/fake_state/bot";
|
import { bot } from "../../__test_support__/fake_state/bot";
|
||||||
import {
|
import {
|
||||||
fakePeripheral, fakeWebcamFeed, fakeSensor
|
fakePeripheral, fakeWebcamFeed, fakeSensor,
|
||||||
} from "../../__test_support__/fake_state/resources";
|
} from "../../__test_support__/fake_state/resources";
|
||||||
import { Dictionary } from "farmbot";
|
import { Dictionary } from "farmbot";
|
||||||
import { Props } from "../interfaces";
|
import { Props } from "../interfaces";
|
||||||
|
|
|
@ -3,17 +3,19 @@ import { Row, Col } from "../ui/index";
|
||||||
import { AxisDisplayGroupProps } from "./interfaces";
|
import { AxisDisplayGroupProps } from "./interfaces";
|
||||||
import { isNumber } from "lodash";
|
import { isNumber } from "lodash";
|
||||||
import { t } from "../i18next_wrapper";
|
import { t } from "../i18next_wrapper";
|
||||||
|
import { Xyz } from "farmbot";
|
||||||
|
|
||||||
const Axis = ({ val }: { val: number | undefined }) => <Col xs={3}>
|
const Axis = ({ axis, val }: { val: number | undefined, axis: Xyz }) =>
|
||||||
<input disabled value={isNumber(val) ? val : "---"} />
|
<Col xs={3}>
|
||||||
</Col>;
|
<input disabled name={axis} value={isNumber(val) ? val : "---"} />
|
||||||
|
</Col>;
|
||||||
|
|
||||||
export const AxisDisplayGroup = ({ position, label }: AxisDisplayGroupProps) => {
|
export const AxisDisplayGroup = ({ position, label }: AxisDisplayGroupProps) => {
|
||||||
const { x, y, z } = position;
|
const { x, y, z } = position;
|
||||||
return <Row>
|
return <Row>
|
||||||
<Axis val={x} />
|
<Axis axis={"x"} val={x} />
|
||||||
<Axis val={y} />
|
<Axis axis={"y"} val={y} />
|
||||||
<Axis val={z} />
|
<Axis axis={"z"} val={z} />
|
||||||
<Col xs={3}>
|
<Col xs={3}>
|
||||||
<label>
|
<label>
|
||||||
{t(label)}
|
{t(label)}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { Move } from "./move/move";
|
||||||
import { BooleanSetting } from "../session_keys";
|
import { BooleanSetting } from "../session_keys";
|
||||||
import { SensorReadings } from "./sensor_readings/sensor_readings";
|
import { SensorReadings } from "./sensor_readings/sensor_readings";
|
||||||
import { isBotOnline } from "../devices/must_be_online";
|
import { isBotOnline } from "../devices/must_be_online";
|
||||||
|
import { hasSensors } from "../devices/components/firmware_hardware_support";
|
||||||
|
|
||||||
/** Controls page. */
|
/** Controls page. */
|
||||||
export class RawControls extends React.Component<Props, {}> {
|
export class RawControls extends React.Component<Props, {}> {
|
||||||
|
@ -24,7 +25,8 @@ export class RawControls extends React.Component<Props, {}> {
|
||||||
}
|
}
|
||||||
|
|
||||||
get hideSensors() {
|
get hideSensors() {
|
||||||
return this.props.getWebAppConfigVal(BooleanSetting.hide_sensors);
|
return this.props.getWebAppConfigVal(BooleanSetting.hide_sensors)
|
||||||
|
|| !hasSensors(this.props.firmwareHardware);
|
||||||
}
|
}
|
||||||
|
|
||||||
move = () => <Move
|
move = () => <Move
|
||||||
|
@ -38,6 +40,7 @@ export class RawControls extends React.Component<Props, {}> {
|
||||||
getWebAppConfigVal={this.props.getWebAppConfigVal} />
|
getWebAppConfigVal={this.props.getWebAppConfigVal} />
|
||||||
|
|
||||||
peripherals = () => <Peripherals
|
peripherals = () => <Peripherals
|
||||||
|
firmwareHardware={this.props.firmwareHardware}
|
||||||
bot={this.props.bot}
|
bot={this.props.bot}
|
||||||
peripherals={this.props.peripherals}
|
peripherals={this.props.peripherals}
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
|
@ -50,6 +53,7 @@ export class RawControls extends React.Component<Props, {}> {
|
||||||
sensors = () => this.hideSensors
|
sensors = () => this.hideSensors
|
||||||
? <div id="hidden-sensors-widget" />
|
? <div id="hidden-sensors-widget" />
|
||||||
: <Sensors
|
: <Sensors
|
||||||
|
firmwareHardware={this.props.firmwareHardware}
|
||||||
bot={this.props.bot}
|
bot={this.props.bot}
|
||||||
sensors={this.props.sensors}
|
sensors={this.props.sensors}
|
||||||
dispatch={this.props.dispatch}
|
dispatch={this.props.dispatch}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import {
|
import {
|
||||||
BotState, Xyz, BotPosition, ShouldDisplay, UserEnv
|
BotState, Xyz, BotPosition, ShouldDisplay, UserEnv,
|
||||||
} from "../devices/interfaces";
|
} from "../devices/interfaces";
|
||||||
import { Vector3, McuParams, FirmwareHardware } from "farmbot/dist";
|
import { Vector3, McuParams, FirmwareHardware } from "farmbot/dist";
|
||||||
import {
|
import {
|
||||||
TaggedWebcamFeed,
|
TaggedWebcamFeed,
|
||||||
TaggedPeripheral,
|
TaggedPeripheral,
|
||||||
TaggedSensor,
|
TaggedSensor,
|
||||||
TaggedSensorReading
|
TaggedSensorReading,
|
||||||
} from "farmbot";
|
} from "farmbot";
|
||||||
import { NetworkState } from "../connectivity/interfaces";
|
import { NetworkState } from "../connectivity/interfaces";
|
||||||
import { GetWebAppConfigValue } from "../config_storage/actions";
|
import { GetWebAppConfigValue } from "../config_storage/actions";
|
||||||
|
|
|
@ -15,12 +15,14 @@ export function KeyValEditRow(p: Props) {
|
||||||
return <Row>
|
return <Row>
|
||||||
<Col xs={6}>
|
<Col xs={6}>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
|
name="label"
|
||||||
placeholder={p.labelPlaceholder}
|
placeholder={p.labelPlaceholder}
|
||||||
value={p.label}
|
value={p.label}
|
||||||
onChange={p.onLabelChange} />
|
onChange={p.onLabelChange} />
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={4}>
|
<Col xs={4}>
|
||||||
<input type={p.valueType}
|
<input type={p.valueType}
|
||||||
|
name="value"
|
||||||
value={p.value}
|
value={p.value}
|
||||||
placeholder={p.valuePlaceholder}
|
placeholder={p.valuePlaceholder}
|
||||||
onChange={p.onValueChange} />
|
onChange={p.onValueChange} />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {
|
import {
|
||||||
calcMicrostepsPerMm, calculateAxialLengths
|
calcMicrostepsPerMm, calculateAxialLengths,
|
||||||
} from "../direction_axes_props";
|
} from "../direction_axes_props";
|
||||||
import { fakeFirmwareConfig } from "../../../__test_support__/fake_state/resources";
|
import { fakeFirmwareConfig } from "../../../__test_support__/fake_state/resources";
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ jest.mock("../../../device", () => ({
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { mount } from "enzyme";
|
import { mount } from "enzyme";
|
||||||
import {
|
import {
|
||||||
DirectionButton, directionDisabled, calculateDistance
|
DirectionButton, directionDisabled, calculateDistance,
|
||||||
} from "../direction_button";
|
} from "../direction_button";
|
||||||
import { DirectionButtonProps } from "../interfaces";
|
import { DirectionButtonProps } from "../interfaces";
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import * as React from "react";
|
||||||
import { mount } from "enzyme";
|
import { mount } from "enzyme";
|
||||||
import { BooleanSetting } from "../../../session_keys";
|
import { BooleanSetting } from "../../../session_keys";
|
||||||
import {
|
import {
|
||||||
moveWidgetSetting, MoveWidgetSettingsMenu, MoveWidgetSettingsMenuProps
|
moveWidgetSetting, MoveWidgetSettingsMenu, MoveWidgetSettingsMenuProps,
|
||||||
} from "../settings_menu";
|
} from "../settings_menu";
|
||||||
|
|
||||||
describe("moveWidgetSetting()", () => {
|
describe("moveWidgetSetting()", () => {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { AxisInputBoxGroup } from "../axis_input_box_group";
|
||||||
import { GetWebAppBool } from "./interfaces";
|
import { GetWebAppBool } from "./interfaces";
|
||||||
import { BooleanSetting } from "../../session_keys";
|
import { BooleanSetting } from "../../session_keys";
|
||||||
import { t } from "../../i18next_wrapper";
|
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";
|
import { FirmwareHardware } from "farmbot";
|
||||||
|
|
||||||
export interface BotPositionRowsProps {
|
export interface BotPositionRowsProps {
|
||||||
|
@ -19,7 +19,7 @@ export interface BotPositionRowsProps {
|
||||||
|
|
||||||
export const BotPositionRows = (props: BotPositionRowsProps) => {
|
export const BotPositionRows = (props: BotPositionRowsProps) => {
|
||||||
const { locationData, getValue, arduinoBusy } = props;
|
const { locationData, getValue, arduinoBusy } = props;
|
||||||
return <div>
|
return <div className={"bot-position-rows"}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={3}>
|
<Col xs={3}>
|
||||||
<label>{t("X AXIS")}</label>
|
<label>{t("X AXIS")}</label>
|
||||||
|
@ -34,12 +34,12 @@ export const BotPositionRows = (props: BotPositionRowsProps) => {
|
||||||
<AxisDisplayGroup
|
<AxisDisplayGroup
|
||||||
position={locationData.position}
|
position={locationData.position}
|
||||||
label={t("Motor Coordinates (mm)")} />
|
label={t("Motor Coordinates (mm)")} />
|
||||||
{!isExpressBoard(props.firmwareHardware) &&
|
{hasEncoders(props.firmwareHardware) &&
|
||||||
getValue(BooleanSetting.scaled_encoders) &&
|
getValue(BooleanSetting.scaled_encoders) &&
|
||||||
<AxisDisplayGroup
|
<AxisDisplayGroup
|
||||||
position={locationData.scaled_encoders}
|
position={locationData.scaled_encoders}
|
||||||
label={t("Scaled Encoder (mm)")} />}
|
label={t("Scaled Encoder (mm)")} />}
|
||||||
{!isExpressBoard(props.firmwareHardware) &&
|
{hasEncoders(props.firmwareHardware) &&
|
||||||
getValue(BooleanSetting.raw_encoders) &&
|
getValue(BooleanSetting.raw_encoders) &&
|
||||||
<AxisDisplayGroup
|
<AxisDisplayGroup
|
||||||
position={locationData.raw_encoders}
|
position={locationData.raw_encoders}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { getDevice } from "../../device";
|
||||||
import { buildDirectionProps } from "./direction_axes_props";
|
import { buildDirectionProps } from "./direction_axes_props";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import {
|
import {
|
||||||
cameraBtnProps
|
cameraBtnProps,
|
||||||
} from "../../devices/components/fbos_settings/camera_selection";
|
} from "../../devices/components/fbos_settings/camera_selection";
|
||||||
const DEFAULT_STEP_SIZE = 100;
|
const DEFAULT_STEP_SIZE = 100;
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const JogControlsGroup = (props: JogControlsGroupProps) => {
|
||||||
const {
|
const {
|
||||||
dispatch, stepSize, botPosition, getValue, arduinoBusy, firmwareSettings
|
dispatch, stepSize, botPosition, getValue, arduinoBusy, firmwareSettings
|
||||||
} = props;
|
} = props;
|
||||||
return <div>
|
return <div className={"jog-controls-group"}>
|
||||||
<label className="text-center">
|
<label className="text-center">
|
||||||
{t("MOVE AMOUNT (mm)")}
|
{t("MOVE AMOUNT (mm)")}
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import moment from "moment";
|
||||||
import { BotLocationData, BotPosition } from "../../devices/interfaces";
|
import { BotLocationData, BotPosition } from "../../devices/interfaces";
|
||||||
import { trim } from "../../util";
|
import { trim } from "../../util";
|
||||||
import {
|
import {
|
||||||
cloneDeep, max, get, isNumber, isEqual, takeRight, ceil, range
|
cloneDeep, max, get, isNumber, isEqual, takeRight, ceil, range,
|
||||||
} from "lodash";
|
} from "lodash";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
|
|
||||||
|
@ -46,9 +46,9 @@ const getLastEntry = (): Entry | undefined => {
|
||||||
const findYLimit = (): number => {
|
const findYLimit = (): number => {
|
||||||
const array = getArray();
|
const array = getArray();
|
||||||
const arrayAbsMax = max(array.map(entry =>
|
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) =>
|
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);
|
return Math.max(ceil(arrayAbsMax || 0, -2), DEFAULT_Y_MAX);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -80,19 +80,19 @@ const getPaths = (): Paths => {
|
||||||
const paths = newPaths();
|
const paths = newPaths();
|
||||||
if (last) {
|
if (last) {
|
||||||
getReversedArray().map(entry => {
|
getReversedArray().map(entry => {
|
||||||
["position", "scaled_encoders"].map((name: LocationName) => {
|
["position", "scaled_encoders"].map((key: LocationName) => {
|
||||||
["x", "y", "z"].map((axis: Xyz) => {
|
["x", "y", "z"].map((axis: Xyz) => {
|
||||||
const lastPos = last.locationData[name][axis];
|
const lastPos = last.locationData[key][axis];
|
||||||
const pos = entry.locationData[name][axis];
|
const pos = entry.locationData[key][axis];
|
||||||
if (isNumber(lastPos) && isFinite(lastPos)
|
if (isNumber(lastPos) && isFinite(lastPos)
|
||||||
&& isNumber(maxY) && isNumber(pos)) {
|
&& isNumber(maxY) && isNumber(pos)) {
|
||||||
if (!paths[name][axis].startsWith("M")) {
|
if (!paths[key][axis].startsWith("M")) {
|
||||||
const yStart = -lastPos / maxY * HEIGHT / 2;
|
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 x = MAX_X - (last.timestamp - entry.timestamp);
|
||||||
const y = -pos / maxY * HEIGHT / 2;
|
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 });
|
updateArray({ timestamp: moment().unix(), locationData });
|
||||||
const paths = getPaths();
|
const paths = getPaths();
|
||||||
return <g id="plot_lines">
|
return <g id="plot_lines">
|
||||||
{["position", "scaled_encoders"].map((name: LocationName) =>
|
{["position", "scaled_encoders"].map((key: LocationName) =>
|
||||||
["x", "y", "z"].map((axis: Xyz) =>
|
["x", "y", "z"].map((axis: Xyz) =>
|
||||||
<path key={name + axis} fill={"none"}
|
<path key={key + axis} fill={"none"}
|
||||||
stroke={COLOR_LOOKUP[axis]} strokeWidth={LINEWIDTH_LOOKUP[name]}
|
stroke={COLOR_LOOKUP[axis]} strokeWidth={LINEWIDTH_LOOKUP[key]}
|
||||||
strokeLinecap={"round"} strokeLinejoin={"round"}
|
strokeLinecap={"round"} strokeLinejoin={"round"}
|
||||||
d={paths[name][axis]} />))}
|
d={paths[key][axis]} />))}
|
||||||
</g>;
|
</g>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app";
|
||||||
import { DevSettings } from "../../account/dev/dev_support";
|
import { DevSettings } from "../../account/dev/dev_support";
|
||||||
import { t } from "../../i18next_wrapper";
|
import { t } from "../../i18next_wrapper";
|
||||||
import { FirmwareHardware } from "farmbot";
|
import { FirmwareHardware } from "farmbot";
|
||||||
import { isExpressBoard } from "../../devices/components/firmware_hardware_support";
|
import { hasEncoders } from "../../devices/components/firmware_hardware_support";
|
||||||
|
|
||||||
export const moveWidgetSetting =
|
export const moveWidgetSetting =
|
||||||
(toggle: ToggleWebAppBool, getValue: GetWebAppBool) =>
|
(toggle: ToggleWebAppBool, getValue: GetWebAppBool) =>
|
||||||
|
@ -27,7 +27,7 @@ export interface MoveWidgetSettingsMenuProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MoveWidgetSettingsMenu = (
|
export const MoveWidgetSettingsMenu = (
|
||||||
{ toggle, getValue, firmwareHardware }: MoveWidgetSettingsMenuProps
|
{ toggle, getValue, firmwareHardware }: MoveWidgetSettingsMenuProps,
|
||||||
) => {
|
) => {
|
||||||
const Setting = moveWidgetSetting(toggle, getValue);
|
const Setting = moveWidgetSetting(toggle, getValue);
|
||||||
return <div className="move-settings-menu">
|
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("Y Axis")} setting={BooleanSetting.y_axis_inverted} />
|
||||||
<Setting label={t("Z Axis")} setting={BooleanSetting.z_axis_inverted} />
|
<Setting label={t("Z Axis")} setting={BooleanSetting.z_axis_inverted} />
|
||||||
|
|
||||||
{!isExpressBoard(firmwareHardware) &&
|
{hasEncoders(firmwareHardware) &&
|
||||||
<div className="display-encoder-data">
|
<div className="display-encoder-data">
|
||||||
<p>{t("Display Encoder Data")}</p>
|
<p>{t("Display Encoder Data")}</p>
|
||||||
<Setting
|
<Setting
|
||||||
|
@ -56,7 +56,7 @@ export const MoveWidgetSettingsMenu = (
|
||||||
setting={BooleanSetting.home_button_homing} />
|
setting={BooleanSetting.home_button_homing} />
|
||||||
|
|
||||||
{DevSettings.futureFeaturesEnabled() &&
|
{DevSettings.futureFeaturesEnabled() &&
|
||||||
<div>
|
<div className={"motor-position-plot-setting-row"}>
|
||||||
<p>{t("Motor position plot")}</p>
|
<p>{t("Motor position plot")}</p>
|
||||||
<Setting
|
<Setting
|
||||||
label={t("show")}
|
label={t("show")}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { StepSizeSelectorProps } from "./interfaces";
|
import { StepSizeSelectorProps } from "./interfaces";
|
||||||
import { first, last } from "lodash";
|
import { first, last } from "lodash";
|
||||||
|
import { t } from "../../i18next_wrapper";
|
||||||
|
|
||||||
export class StepSizeSelector extends React.Component<StepSizeSelectorProps, {}> {
|
export class StepSizeSelector extends React.Component<StepSizeSelectorProps, {}> {
|
||||||
cssForIndex(num: number) {
|
cssForIndex(num: number) {
|
||||||
|
@ -20,16 +21,13 @@ export class StepSizeSelector extends React.Component<StepSizeSelectorProps, {}>
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div className="move-amount-wrapper">
|
return <div className="move-amount-wrapper">
|
||||||
{
|
{this.props.choices.map((item: number, inx: number) =>
|
||||||
this.props.choices.map(
|
<button key={inx}
|
||||||
(item: number, inx: number) => <button
|
title={t("{{ amount }}mm", { amount: item })}
|
||||||
className={this.cssForIndex(item)}
|
className={this.cssForIndex(item)}
|
||||||
onClick={() => this.props.selector(item)}
|
onClick={() => this.props.selector(item)}>
|
||||||
key={inx}>
|
{item}
|
||||||
{item}
|
</button>)}
|
||||||
</button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { bot } from "../../../__test_support__/fake_state/bot";
|
||||||
import { PeripheralsProps } from "../../../devices/interfaces";
|
import { PeripheralsProps } from "../../../devices/interfaces";
|
||||||
import { fakePeripheral } from "../../../__test_support__/fake_state/resources";
|
import { fakePeripheral } from "../../../__test_support__/fake_state/resources";
|
||||||
import { clickButton } from "../../../__test_support__/helpers";
|
import { clickButton } from "../../../__test_support__/helpers";
|
||||||
import { SpecialStatus } from "farmbot";
|
import { SpecialStatus, FirmwareHardware } from "farmbot";
|
||||||
import { error } from "../../../toast/toast";
|
import { error } from "../../../toast/toast";
|
||||||
|
|
||||||
describe("<Peripherals />", () => {
|
describe("<Peripherals />", () => {
|
||||||
|
@ -14,7 +14,8 @@ describe("<Peripherals />", () => {
|
||||||
bot,
|
bot,
|
||||||
peripherals: [fakePeripheral()],
|
peripherals: [fakePeripheral()],
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
disabled: false
|
disabled: false,
|
||||||
|
firmwareHardware: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,11 +74,28 @@ describe("<Peripherals />", () => {
|
||||||
expect(p.dispatch).toHaveBeenCalled();
|
expect(p.dispatch).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("adds farmduino peripherals", () => {
|
it.each<[FirmwareHardware, number]>([
|
||||||
|
["arduino", 2],
|
||||||
|
["farmduino", 5],
|
||||||
|
["farmduino_k14", 5],
|
||||||
|
["farmduino_k15", 5],
|
||||||
|
["express_k10", 3],
|
||||||
|
])("adds peripherals: %s", (firmware, expectedAdds) => {
|
||||||
const p = fakeProps();
|
const p = fakeProps();
|
||||||
|
p.firmwareHardware = firmware;
|
||||||
const wrapper = mount(<Peripherals {...p} />);
|
const wrapper = mount(<Peripherals {...p} />);
|
||||||
wrapper.setState({ isEditing: true });
|
wrapper.setState({ isEditing: true });
|
||||||
clickButton(wrapper, 3, "farmduino");
|
clickButton(wrapper, 3, "stock");
|
||||||
expect(p.dispatch).toHaveBeenCalledTimes(5);
|
expect(p.dispatch).toHaveBeenCalledTimes(expectedAdds);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hides stock button", () => {
|
||||||
|
const p = fakeProps();
|
||||||
|
p.firmwareHardware = "none";
|
||||||
|
const wrapper = mount(<Peripherals {...p} />);
|
||||||
|
wrapper.setState({ isEditing: true });
|
||||||
|
const btn = wrapper.find("button").at(3);
|
||||||
|
expect(btn.text().toLowerCase()).toContain("stock");
|
||||||
|
expect(btn.props().hidden).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { mount } from "enzyme";
|
||||||
import { PeripheralList } from "../peripheral_list";
|
import { PeripheralList } from "../peripheral_list";
|
||||||
import {
|
import {
|
||||||
TaggedPeripheral,
|
TaggedPeripheral,
|
||||||
SpecialStatus
|
SpecialStatus,
|
||||||
} from "farmbot";
|
} from "farmbot";
|
||||||
import { Pins } from "farmbot/dist";
|
import { Pins } from "farmbot/dist";
|
||||||
|
|
||||||
|
|
|
@ -51,31 +51,52 @@ export class Peripherals
|
||||||
|
|
||||||
newPeripheral = (
|
newPeripheral = (
|
||||||
pin: number | undefined = undefined,
|
pin: number | undefined = undefined,
|
||||||
label = t("New Peripheral")
|
label = t("New Peripheral"),
|
||||||
) => {
|
) => {
|
||||||
this.props.dispatch(init("Peripheral", { pin, label }));
|
this.props.dispatch(init("Peripheral", { pin, label }));
|
||||||
};
|
};
|
||||||
|
|
||||||
farmduinoPeripherals = () => {
|
get stockPeripherals() {
|
||||||
this.newPeripheral(7, t("Lighting"));
|
switch (this.props.firmwareHardware) {
|
||||||
this.newPeripheral(8, t("Water"));
|
case "arduino":
|
||||||
this.newPeripheral(9, t("Vacuum"));
|
return [
|
||||||
this.newPeripheral(10, t("Peripheral ") + "4");
|
{ pin: 8, label: t("Water") },
|
||||||
this.newPeripheral(12, t("Peripheral ") + "5");
|
{ pin: 9, label: t("Vacuum") },
|
||||||
|
];
|
||||||
|
case "farmduino":
|
||||||
|
case "farmduino_k14":
|
||||||
|
case "farmduino_k15":
|
||||||
|
default:
|
||||||
|
return [
|
||||||
|
{ pin: 7, label: t("Lighting") },
|
||||||
|
{ pin: 8, label: t("Water") },
|
||||||
|
{ pin: 9, label: t("Vacuum") },
|
||||||
|
{ pin: 10, label: t("Peripheral ") + "4" },
|
||||||
|
{ pin: 12, label: t("Peripheral ") + "5" },
|
||||||
|
];
|
||||||
|
case "express_k10":
|
||||||
|
return [
|
||||||
|
{ pin: 7, label: t("Lighting") },
|
||||||
|
{ pin: 8, label: t("Water") },
|
||||||
|
{ pin: 9, label: t("Vacuum") },
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { isEditing } = this.state;
|
const { isEditing } = this.state;
|
||||||
const status = getArrayStatus(this.props.peripherals);
|
const status = getArrayStatus(this.props.peripherals);
|
||||||
|
const editButtonText = isEditing
|
||||||
|
? t("Back")
|
||||||
|
: t("Edit");
|
||||||
return <Widget className="peripherals-widget">
|
return <Widget className="peripherals-widget">
|
||||||
<WidgetHeader title={t("Peripherals")} helpText={ToolTips.PERIPHERALS}>
|
<WidgetHeader title={t("Peripherals")} helpText={ToolTips.PERIPHERALS}>
|
||||||
<button
|
<button
|
||||||
className="fb-button gray"
|
className="fb-button gray"
|
||||||
onClick={this.toggle}
|
onClick={this.toggle}
|
||||||
|
title={editButtonText}
|
||||||
disabled={!!status && isEditing}>
|
disabled={!!status && isEditing}>
|
||||||
{!isEditing && t("Edit")}
|
{editButtonText}
|
||||||
{isEditing && t("Back")}
|
|
||||||
</button>
|
</button>
|
||||||
<SaveBtn
|
<SaveBtn
|
||||||
hidden={!isEditing}
|
hidden={!isEditing}
|
||||||
|
@ -85,17 +106,20 @@ export class Peripherals
|
||||||
hidden={!isEditing}
|
hidden={!isEditing}
|
||||||
className="fb-button green"
|
className="fb-button green"
|
||||||
type="button"
|
type="button"
|
||||||
|
title={t("add peripheral")}
|
||||||
onClick={() => this.newPeripheral()}>
|
onClick={() => this.newPeripheral()}>
|
||||||
<i className="fa fa-plus" />
|
<i className="fa fa-plus" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
hidden={!isEditing}
|
hidden={!isEditing || this.props.firmwareHardware == "none"}
|
||||||
className="fb-button green"
|
className="fb-button green"
|
||||||
type="button"
|
type="button"
|
||||||
onClick={this.farmduinoPeripherals}>
|
title={t("add stock peripherals")}
|
||||||
|
onClick={() => this.stockPeripherals.map(p =>
|
||||||
|
this.newPeripheral(p.pin, p.label))}>
|
||||||
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
|
<i className="fa fa-plus" style={{ marginRight: "0.5rem" }} />
|
||||||
Farmduino
|
{t("Stock")}
|
||||||
</button>
|
</button>
|
||||||
</WidgetHeader>
|
</WidgetHeader>
|
||||||
<WidgetBody>
|
<WidgetBody>
|
||||||
{this.showPins()}
|
{this.showPins()}
|
||||||
|
|
|
@ -26,6 +26,5 @@ export const PeripheralForm = (props: PeripheralFormProps) =>
|
||||||
dispatch={props.dispatch}
|
dispatch={props.dispatch}
|
||||||
uuid={peripheral.uuid} />
|
uuid={peripheral.uuid} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>)}
|
||||||
)}
|
|
||||||
</div>;
|
</div>;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue