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