diff --git a/.ruby-version b/.ruby-version index 86fb650440..37d02a6e38 100755 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.7 +3.3.8 diff --git a/Gemfile b/Gemfile index 8019878b75..d34325c08a 100755 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ source "https://rubygems.org" -ruby "~> 3.3.7" +ruby "~> 3.3.8" gem "rails", "~> 6" gem "active_model_serializers" @@ -28,7 +28,6 @@ gem "tzinfo-data" # For validation of user selected timezone names gem "valid_url" gem "thwait" gem "lograge" # Used to filter repetitive RabbitMQ logs. -gem "scout_apm" group :development, :test do gem "climate_control" diff --git a/Gemfile.lock b/Gemfile.lock index 6bd053f870..949f4fa6a7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -68,7 +68,6 @@ GEM addressable (2.8.7) public_suffix (>= 2.0.2, < 7.0) amq-protocol (2.3.3) - ast (2.4.3) base64 (0.2.0) bcrypt (3.1.20) bigdecimal (3.1.9) @@ -119,7 +118,7 @@ GEM railties (>= 5.0.0) faker (3.5.1) i18n (>= 1.8.11, < 2) - faraday (2.13.0) + faraday (2.13.1) faraday-net_http (>= 2.0, < 3.5) json logger @@ -129,7 +128,7 @@ GEM net-http (>= 0.5.0) globalid (1.2.1) activesupport (>= 6.1) - google-apis-core (0.16.0) + google-apis-core (0.17.0) addressable (~> 2.5, >= 2.5.1) googleauth (~> 1.9) httpclient (>= 2.8.3, < 3.a) @@ -137,18 +136,18 @@ GEM mutex_m representable (~> 3.0) retriable (>= 2.0, < 4.a) - google-apis-iamcredentials_v1 (0.22.0) + google-apis-iamcredentials_v1 (0.23.0) google-apis-core (>= 0.15.0, < 2.a) - google-apis-storage_v1 (0.50.0) + google-apis-storage_v1 (0.51.0) google-apis-core (>= 0.15.0, < 2.a) google-cloud-core (1.8.0) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) - google-cloud-env (2.2.2) + google-cloud-env (2.3.0) base64 (~> 0.2) faraday (>= 1.0, < 3.a) google-cloud-errors (1.5.0) - google-cloud-storage (1.55.0) + google-cloud-storage (1.56.0) addressable (~> 2.8) digest-crc (~> 0.4) google-apis-core (~> 0.13) @@ -157,7 +156,7 @@ GEM google-cloud-core (~> 1.6) googleauth (~> 1.9) mini_mime (~> 1.0) - google-logging-utils (0.1.0) + google-logging-utils (0.2.0) googleauth (1.14.0) faraday (>= 1.0, < 3.a) google-cloud-env (~> 2.2) @@ -172,7 +171,7 @@ GEM mutex_m i18n (1.14.7) concurrent-ruby (~> 1.0) - json (2.10.2) + json (2.11.3) jsonapi-renderer (0.2.2) jwt (2.10.1) base64 @@ -212,7 +211,7 @@ GEM mutex_m (0.3.0) net-http (0.6.0) uri - net-imap (0.5.6) + net-imap (0.5.8) date net-protocol net-pop (0.1.2) @@ -222,15 +221,12 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.4) - nokogiri (1.18.7-aarch64-linux-gnu) + nokogiri (1.18.8-aarch64-linux-gnu) racc (~> 1.4) - nokogiri (1.18.7-x86_64-linux-gnu) + nokogiri (1.18.8-x86_64-linux-gnu) racc (~> 1.4) orm_adapter (0.5.0) os (1.1.4) - parser (3.3.7.4) - ast (~> 2.4.1) - racc passenger (6.0.27) rack (>= 1.6.13) rackup (>= 1.0.1) @@ -241,7 +237,7 @@ GEM method_source (~> 1.0) pry-rails (0.3.11) pry (>= 0.13.0) - public_suffix (6.0.1) + public_suffix (6.0.2) rabbitmq_http_api_client (3.0.0) addressable (~> 2.7) faraday (~> 2.9) @@ -313,10 +309,10 @@ GEM rspec-mocks (~> 3.13.0) rspec-core (3.13.3) rspec-support (~> 3.13.0) - rspec-expectations (3.13.3) + rspec-expectations (3.13.4) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.2) + rspec-mocks (3.13.4) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-rails (6.1.5) @@ -327,17 +323,15 @@ GEM rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) rspec-support (~> 3.13) - rspec-support (3.13.2) + rspec-support (3.13.3) rspec_junit_formatter (0.6.0) rspec-core (>= 2, < 4, != 2.12.0) scenic (1.8.0) activerecord (>= 4.0.0) railties (>= 4.0.0) - scout_apm (5.6.2) - parser secure_headers (7.1.0) set (1.1.1) - signet (0.19.0) + signet (0.20.0) addressable (~> 2.8) faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) @@ -354,8 +348,9 @@ GEM sorted_set (1.0.3) rbtree set (~> 1.0) - sprockets (4.2.1) + sprockets (4.2.2) concurrent-ruby (~> 1.0) + logger rack (>= 2.2.4, < 4) sprockets-rails (3.5.2) actionpack (>= 6.1) @@ -426,7 +421,6 @@ DEPENDENCIES rspec-rails rspec_junit_formatter scenic - scout_apm secure_headers simplecov simplecov-cobertura @@ -437,7 +431,7 @@ DEPENDENCIES webmock RUBY VERSION - ruby 3.3.7p123 + ruby 3.3.8p144 BUNDLED WITH - 2.6.5 + 2.6.8 diff --git a/app/models/fbos_config.rb b/app/models/fbos_config.rb index 22e5ab2cb7..0efa40c7de 100644 --- a/app/models/fbos_config.rb +++ b/app/models/fbos_config.rb @@ -13,6 +13,7 @@ class MissingSerial < StandardError; end FARMDUINO_K15 = "farmduino_k15", FARMDUINO_K16 = "farmduino_k16", FARMDUINO_K17 = "farmduino_k17", + FARMDUINO_K18 = "farmduino_k18", EXPRESS_K10 = "express_k10", EXPRESS_K11 = "express_k11", EXPRESS_K12 = "express_k12", diff --git a/app/mutations/devices/create_seed_data.rb b/app/mutations/devices/create_seed_data.rb index 5afd533b92..66a9a0e40d 100644 --- a/app/mutations/devices/create_seed_data.rb +++ b/app/mutations/devices/create_seed_data.rb @@ -14,10 +14,12 @@ class CreateSeedData < Mutations::Command "genesis_1.5" => Devices::Seeders::GenesisOneFive, "genesis_1.6" => Devices::Seeders::GenesisOneSix, "genesis_1.7" => Devices::Seeders::GenesisOneSeven, + "genesis_1.8" => Devices::Seeders::GenesisOneEight, "genesis_xl_1.4" => Devices::Seeders::GenesisXlOneFour, "genesis_xl_1.5" => Devices::Seeders::GenesisXlOneFive, "genesis_xl_1.6" => Devices::Seeders::GenesisXlOneSix, "genesis_xl_1.7" => Devices::Seeders::GenesisXlOneSeven, + "genesis_xl_1.8" => Devices::Seeders::GenesisXlOneEight, "none" => Devices::Seeders::None, } @@ -41,12 +43,16 @@ def seeder end def run_seeds! + if demo + Devices::Seeders::DemoAccountSeeder.new(device).before_product_line_seeder + end + seeder.class::COMMAND_ORDER.map do |cmd| seeder.send(cmd) end if demo - Devices::Seeders::DemoAccountSeeder.new(device).misc(product_line) + Devices::Seeders::DemoAccountSeeder.new(device).after_product_line_seeder(product_line) end end end diff --git a/app/mutations/devices/seeders/abstract_express.rb b/app/mutations/devices/seeders/abstract_express.rb index b941a82229..adf301e2d6 100644 --- a/app/mutations/devices/seeders/abstract_express.rb +++ b/app/mutations/devices/seeders/abstract_express.rb @@ -109,13 +109,19 @@ def settings_gantry_height end def settings_default_map_size_y - device.web_app_config.update!(map_size_y: 1_200) + device.web_app_config.update!(map_size_y: 930) end def settings_hide_sensors device.web_app_config.update!(hide_sensors: true) end + def settings_three_d + FarmwareEnvs::Create.run( + {key: "3D_beamLength", value: "1200"}, + device: device) + end + private def seed_trough_1_id diff --git a/app/mutations/devices/seeders/abstract_seeder.rb b/app/mutations/devices/seeders/abstract_seeder.rb index 7b8a5b33cb..c1cd8fb13a 100644 --- a/app/mutations/devices/seeders/abstract_seeder.rb +++ b/app/mutations/devices/seeders/abstract_seeder.rb @@ -6,13 +6,7 @@ class AbstractSeeder # DO NOT ALPHABETIZE. ORDER MATTERS! - RC COMMAND_ORDER = [ - # PLANTS ================================= - :plants, - # GROUPS ================================= - :point_groups_spinach, - :point_groups_broccoli, - :point_groups_beet, :point_groups_all_plants, :point_groups_all_points, :point_groups_all_weeds, @@ -43,6 +37,7 @@ class AbstractSeeder :settings_firmware, :settings_gantry_height, :settings_hide_sensors, + :settings_three_d, # TOOLS ================================== :tools_seed_bin, @@ -66,9 +61,6 @@ class AbstractSeeder :tool_slots_slot_8, :tool_slots_slot_9, - # WEBCAM FEEDS =========================== - :webcam_feeds, - # SEQUENCES ============================== :sequences_mount_tool, :sequences_dismount_tool, @@ -86,9 +78,6 @@ class AbstractSeeder :sequences_dispense_water, :sequences_mow_all_weeds, :sequences_pick_from_seed_tray, - - # EVERYTHING ELSE ======================== - :misc, ] def initialize(device) @@ -99,8 +88,6 @@ def settings_hide_sensors device.web_app_config.update!(hide_sensors: false) end - def plants; end - def peripherals_lighting add_peripheral(7, ToolNames::LIGHTING) end @@ -142,12 +129,6 @@ def sequences_water_plant Sequences::Create.run!(s, device: device) end - def point_groups_spinach; end - - def point_groups_broccoli; end - - def point_groups_beet; end - def point_groups_all_plants add_point_group(name: "All plants") end @@ -230,6 +211,8 @@ def settings_soil_height device.fbos_config.update!(soil_height: -500) end + def settings_three_d; end + def tool_slots_slot_1; end def tool_slots_slot_2; end def tool_slots_slot_3; end @@ -252,9 +235,6 @@ def tools_watering_nozzle def tools_weeder; end def tools_rotary; end - def webcam_feeds; end - def misc; end - private def install_sequence_version_by_name(name) diff --git a/app/mutations/devices/seeders/demo_account_seeder.rb b/app/mutations/devices/seeders/demo_account_seeder.rb index d27a2ae354..474c205d81 100644 --- a/app/mutations/devices/seeders/demo_account_seeder.rb +++ b/app/mutations/devices/seeders/demo_account_seeder.rb @@ -25,9 +25,9 @@ def create_webcam_feed(product_line) device: device }) end - def plants(product_line) + def add_plants(product_line) spinach_row_count = product_line.include?("xl") ? 28 : 13 - spinach_col_count = product_line.include?("xl") ? 4 : 2 + spinach_col_count = product_line.include?("genesis_xl") ? 4 : 2 (0..(spinach_row_count - 1)).map do |i| (0..(spinach_col_count - 1)).map do |j| Points::Create.run!(device: device, @@ -41,7 +41,13 @@ def plants(product_line) end end broccoli_row_count = product_line.include?("xl") ? 9 : 4 - broccoli_col_count = product_line.include?("xl") ? 3 : 1 + broccoli_col_count = if product_line.include?("genesis_xl") + 3 + elsif product_line.include?("xl") + 2 + else + 1 + end (0..(broccoli_row_count - 1)).map do |i| (0..(broccoli_col_count - 1)).map do |j| Points::Create.run!(device: device, @@ -70,30 +76,26 @@ def plants(product_line) end end - def point_groups_spinach + def add_point_groups add_point_group(name: "Spinach plants", openfarm_slug: "spinach") - end - - def point_groups_broccoli add_point_group(name: "Broccoli plants", openfarm_slug: "broccoli") - end - - def point_groups_beet add_point_group(name: "Beet plants", openfarm_slug: "beet") end - MARKETING_BULLETIN = GlobalBulletin.find_or_create_by(slug: "buy-a-farmbot") do |gb| - gb.href = "https://farm.bot" - gb.href_label = "Visit our website" - gb.slug = "buy-a-farmbot" - gb.title = "Buy a FarmBot" - gb.type = "info" - gb.content = [ - "Ready to get a FarmBot of your own? Check out our website to", - " learn more about our various products. We offer FarmBots at", - " all different price points, sizes, and capabilities so you'", - "re sure to find one that suits your needs.", - ].join("") + def marketing_bulletin + GlobalBulletin.find_or_create_by(slug: "buy-a-farmbot") do |gb| + gb.href = "https://farm.bot" + gb.href_label = "Visit our website" + gb.slug = "buy-a-farmbot" + gb.title = "Buy a FarmBot" + gb.type = "info" + gb.content = [ + "Ready to get a FarmBot of your own? Check out our website to", + " learn more about our various products. We offer FarmBots at", + " all different price points, sizes, and capabilities so you'", + "re sure to find one that suits your needs.", + ].join("") + end end DEMO_ALERTS = [ @@ -115,13 +117,18 @@ def point_groups_beet # tester FBOS version `1000.0.0`. READ_COMMENT_ABOVE = "100.0.0" - def misc(product_line) + def before_product_line_seeder + device + .web_app_config + .update!(discard_unsaved: true, three_d_garden: true) + end + + def after_product_line_seeder(product_line) create_webcam_feed(product_line) - plants(product_line) - point_groups_spinach - point_groups_broccoli - point_groups_beet + add_plants(product_line) + add_point_groups + marketing_bulletin device.alerts.where(problem_tag: UNUSED_ALERTS).destroy_all DEMO_ALERTS .map { |p| p.merge(device: device) } @@ -131,9 +138,6 @@ def misc(product_line) .map { |p| Logs::Create.run!(p) } device .update!(fbos_version: READ_COMMENT_ABOVE) - device - .web_app_config - .update!(discard_unsaved: true) end end end diff --git a/app/mutations/devices/seeders/express_xl_one_one.rb b/app/mutations/devices/seeders/express_xl_one_one.rb index 092c66e178..dd6c9dbe9a 100644 --- a/app/mutations/devices/seeders/express_xl_one_one.rb +++ b/app/mutations/devices/seeders/express_xl_one_one.rb @@ -12,11 +12,17 @@ def settings_firmware end def settings_default_map_size_x - device.web_app_config.update!(map_size_x: 6_000) + 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_400) + device.web_app_config.update!(map_size_y: 2_130) + end + + def settings_three_d + FarmwareEnvs::Create.run( + {key: "3D_beamLength", value: "2400"}, + device: device) end end end diff --git a/app/mutations/devices/seeders/express_xl_one_two.rb b/app/mutations/devices/seeders/express_xl_one_two.rb index 3c795d60a1..3fb3b4c798 100644 --- a/app/mutations/devices/seeders/express_xl_one_two.rb +++ b/app/mutations/devices/seeders/express_xl_one_two.rb @@ -12,11 +12,17 @@ def settings_firmware end def settings_default_map_size_x - device.web_app_config.update!(map_size_x: 6_000) + 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_400) + device.web_app_config.update!(map_size_y: 2_130) + end + + def settings_three_d + FarmwareEnvs::Create.run( + {key: "3D_beamLength", value: "2400"}, + device: device) end end end diff --git a/app/mutations/devices/seeders/express_xl_one_zero.rb b/app/mutations/devices/seeders/express_xl_one_zero.rb index f7f10fd7db..1ed1d97b92 100644 --- a/app/mutations/devices/seeders/express_xl_one_zero.rb +++ b/app/mutations/devices/seeders/express_xl_one_zero.rb @@ -12,11 +12,17 @@ def settings_firmware end def settings_default_map_size_x - device.web_app_config.update!(map_size_x: 6_000) + 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_400) + device.web_app_config.update!(map_size_y: 2_130) + end + + def settings_three_d + FarmwareEnvs::Create.run( + {key: "3D_beamLength", value: "2400"}, + device: device) end end end diff --git a/app/mutations/devices/seeders/genesis_one_eight.rb b/app/mutations/devices/seeders/genesis_one_eight.rb new file mode 100644 index 0000000000..142bf0cb0b --- /dev/null +++ b/app/mutations/devices/seeders/genesis_one_eight.rb @@ -0,0 +1,74 @@ +module Devices + module Seeders + class GenesisOneEight < AbstractGenesis + def settings_firmware + device + .fbos_config + .update!(firmware_hardware: FbosConfig::FARMDUINO_K18) + end + + def peripherals_rotary_tool + add_peripheral(2, ToolNames::ROTARY_TOOL) + end + + def peripherals_rotary_tool_reverse + add_peripheral(3, ToolNames::ROTARY_TOOL_REVERSE) + end + + def tool_slots_slot_6 + add_tool_slot(name: ToolNames::ROTARY_TOOL, + x: TOOL_X, + y: TOOL_Y + 7 * TOOL_SPACING, + z: TOOL_Z, + tool: tools_rotary) + end + + def tool_slots_slot_7 + add_tool_slot(name: ToolNames::SEED_TROUGH_1, + x: 0, + y: TROUGH_Y, + z: TROUGH_Z, + 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: TROUGH_Y + TROUGH_SPACING, + z: TROUGH_Z, + tool: tools_seed_trough_2, + pullout_direction: ToolSlot::NONE, + gantry_mounted: true) + end + + def tool_slots_slot_9; end + + def tools_weeder; end + + def tools_rotary + @tools_rotary ||= + add_tool(ToolNames::ROTARY_TOOL) + 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 + + def sequences_mow_all_weeds + success = install_sequence_version_by_name(PublicSequenceNames::MOW_ALL_WEEDS) + if !success + s = SequenceSeeds::MOW_ALL_WEEDS.deep_dup + Sequences::Create.run!(s, device: device) + end + end + end + end +end diff --git a/app/mutations/devices/seeders/genesis_xl_one_eight.rb b/app/mutations/devices/seeders/genesis_xl_one_eight.rb new file mode 100644 index 0000000000..3d3f2b7e57 --- /dev/null +++ b/app/mutations/devices/seeders/genesis_xl_one_eight.rb @@ -0,0 +1,92 @@ +module Devices + module Seeders + class GenesisXlOneEight < AbstractGenesis + def settings_firmware + device + .fbos_config + .update!(firmware_hardware: FbosConfig::FARMDUINO_K18) + end + + def settings_device_name + device.update!(name: Names::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_730) + end + + def peripherals_rotary_tool + add_peripheral(2, ToolNames::ROTARY_TOOL) + end + + def peripherals_rotary_tool_reverse + add_peripheral(3, ToolNames::ROTARY_TOOL_REVERSE) + end + + def tool_slots_slot_6 + add_tool_slot(name: ToolNames::ROTARY_TOOL, + x: TOOL_X, + y: TOOL_Y + 7 * TOOL_SPACING, + z: TOOL_Z, + tool: tools_rotary) + end + + def tool_slots_slot_7 + add_tool_slot(name: ToolNames::SEED_TROUGH_1, + x: 0, + y: TROUGH_Y, + z: TROUGH_Z, + 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: TROUGH_Y + TROUGH_SPACING, + z: TROUGH_Z, + tool: tools_seed_trough_2, + pullout_direction: ToolSlot::NONE, + gantry_mounted: true) + end + + def tool_slots_slot_9; end + + def tools_weeder; end + + def tools_rotary + @tools_rotary ||= + add_tool(ToolNames::ROTARY_TOOL) + 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 + + def sequences_mow_all_weeds + success = install_sequence_version_by_name(PublicSequenceNames::MOW_ALL_WEEDS) + if !success + s = SequenceSeeds::MOW_ALL_WEEDS.deep_dup + Sequences::Create.run!(s, device: device) + end + end + + def settings_three_d + FarmwareEnvs::Create.run( + {key: "3D_beamLength", value: "3000"}, + device: device) + end + end + end +end diff --git a/app/mutations/devices/seeders/genesis_xl_one_five.rb b/app/mutations/devices/seeders/genesis_xl_one_five.rb index 276a090702..ec672dc94c 100644 --- a/app/mutations/devices/seeders/genesis_xl_one_five.rb +++ b/app/mutations/devices/seeders/genesis_xl_one_five.rb @@ -52,6 +52,12 @@ def tools_seed_trough_2 end def tools_rotary; end + + def settings_three_d + FarmwareEnvs::Create.run( + {key: "3D_beamLength", value: "3000"}, + device: device) + end end end end diff --git a/app/mutations/devices/seeders/genesis_xl_one_four.rb b/app/mutations/devices/seeders/genesis_xl_one_four.rb index d8ef17c361..2def1e9319 100644 --- a/app/mutations/devices/seeders/genesis_xl_one_four.rb +++ b/app/mutations/devices/seeders/genesis_xl_one_four.rb @@ -18,6 +18,12 @@ def settings_default_map_size_x def settings_default_map_size_y device.web_app_config.update!(map_size_y: 2_730) end + + def settings_three_d + FarmwareEnvs::Create.run( + {key: "3D_beamLength", value: "3000"}, + device: device) + end end end end diff --git a/app/mutations/devices/seeders/genesis_xl_one_seven.rb b/app/mutations/devices/seeders/genesis_xl_one_seven.rb index e936beabff..9200ecaec1 100644 --- a/app/mutations/devices/seeders/genesis_xl_one_seven.rb +++ b/app/mutations/devices/seeders/genesis_xl_one_seven.rb @@ -81,6 +81,12 @@ def sequences_mow_all_weeds Sequences::Create.run!(s, device: device) end end + + def settings_three_d + FarmwareEnvs::Create.run( + {key: "3D_beamLength", value: "3000"}, + device: device) + end end end end diff --git a/app/mutations/devices/seeders/genesis_xl_one_six.rb b/app/mutations/devices/seeders/genesis_xl_one_six.rb index 4292ccce5e..97482583bd 100644 --- a/app/mutations/devices/seeders/genesis_xl_one_six.rb +++ b/app/mutations/devices/seeders/genesis_xl_one_six.rb @@ -77,6 +77,12 @@ def sequences_mow_all_weeds Sequences::Create.run!(s, device: device) end end + + def settings_three_d + FarmwareEnvs::Create.run( + {key: "3D_beamLength", value: "3000"}, + device: device) + end end end end diff --git a/app/mutations/devices/seeders/none.rb b/app/mutations/devices/seeders/none.rb index 0ef2ce22c1..ec697cd548 100644 --- a/app/mutations/devices/seeders/none.rb +++ b/app/mutations/devices/seeders/none.rb @@ -10,7 +10,6 @@ def peripherals_rotary_tool; end def peripherals_rotary_tool_reverse; end def pin_bindings_button_1; end def pin_bindings_button_2; end - def plants; end def sensors_soil_sensor; end def sensors_tool_verification; end def sequences_mount_tool; end @@ -21,9 +20,6 @@ def sequences_take_photo_of_plant; end def sequences_water_plant; end def sequences_mow_all_weeds; end def sequences_pick_from_seed_tray; end - def point_groups_spinach; end - def point_groups_broccoli; end - def point_groups_beet; end def point_groups_all_plants; end def point_groups_all_points; end def point_groups_all_weeds; end diff --git a/db/migrate/20250502201109_change_dark_mode_default.rb b/db/migrate/20250502201109_change_dark_mode_default.rb new file mode 100644 index 0000000000..5a75fde62a --- /dev/null +++ b/db/migrate/20250502201109_change_dark_mode_default.rb @@ -0,0 +1,5 @@ +class ChangeDarkModeDefault < ActiveRecord::Migration[6.1] + def change + change_column_default(:web_app_configs, :dark_mode, from: false, to: true) + end +end diff --git a/db/structure.sql b/db/structure.sql index 60712aa0db..8576fe8b44 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -2034,7 +2034,7 @@ CREATE TABLE public.web_app_configs ( show_missed_step_plot boolean DEFAULT false, enable_3d_electronics_box_top boolean DEFAULT true, three_d_garden boolean DEFAULT false, - dark_mode boolean DEFAULT false + dark_mode boolean DEFAULT true ); @@ -3981,6 +3981,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240625195838'), ('20241203194030'), ('20241203211516'), -('20250221191831'); +('20250221191831'), +('20250502201109'); diff --git a/docker_configs/api.Dockerfile b/docker_configs/api.Dockerfile index 741be04228..f498481088 100644 --- a/docker_configs/api.Dockerfile +++ b/docker_configs/api.Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:3.3.7 +FROM ruby:3.3.8 RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg > /dev/null && \ sh -c '. /etc/os-release; echo $VERSION_CODENAME; echo "deb http://apt.postgresql.org/pub/repos/apt/ $VERSION_CODENAME-pgdg main" >> /etc/apt/sources.list.d/pgdg.list' && \ apt-get update -qq && apt-get install -y build-essential libpq-dev postgresql postgresql-contrib && \ diff --git a/frontend/__test_support__/fake_state/resources.ts b/frontend/__test_support__/fake_state/resources.ts index 051113a369..f63a482b5a 100644 --- a/frontend/__test_support__/fake_state/resources.ts +++ b/frontend/__test_support__/fake_state/resources.ts @@ -36,7 +36,6 @@ import { } from "farmbot/dist/resources/api_resources"; import { MessageType } from "../../sequences/interfaces"; import { TaggedPointGroup } from "../../resources/interfaces"; -import { BooleanConfigKey } from "farmbot/dist/resources/configs/web_app"; export const resources: Everything["resources"] = buildResourceIndex(); let idCounter = 1; @@ -391,8 +390,8 @@ export function fakeWebAppConfig(): TaggedWebAppConfig { map_size_x: 2900, map_size_y: 1230, user_interface_read_only_mode: false, - ["three_d_garden" as BooleanConfigKey]: false, - ["dark_mode" as BooleanConfigKey]: false, + three_d_garden: false, + dark_mode: true, view_celery_script: false, }); } diff --git a/frontend/controls/peripherals/__tests__/index_test.tsx b/frontend/controls/peripherals/__tests__/index_test.tsx index 1f36525ca1..84c3121a0c 100644 --- a/frontend/controls/peripherals/__tests__/index_test.tsx +++ b/frontend/controls/peripherals/__tests__/index_test.tsx @@ -84,6 +84,7 @@ describe("", () => { ["farmduino_k15", 5], ["farmduino_k16", 7], ["farmduino_k17", 7], + ["farmduino_k18", 7], ["express_k10", 3], ["express_k11", 3], ["express_k12", 3], diff --git a/frontend/controls/peripherals/index.tsx b/frontend/controls/peripherals/index.tsx index fc3f7cfa5e..2d606430f6 100644 --- a/frontend/controls/peripherals/index.tsx +++ b/frontend/controls/peripherals/index.tsx @@ -103,6 +103,7 @@ export class Peripherals ]; case "farmduino_k16": case "farmduino_k17": + case "farmduino_k18": return [ ...BASE_PERIPHERALS, ...ROTARY_TOOL, diff --git a/frontend/css/farm_designer/three_d_garden.scss b/frontend/css/farm_designer/three_d_garden.scss index eff5d11360..c6bf5855ce 100644 --- a/frontend/css/farm_designer/three_d_garden.scss +++ b/frontend/css/farm_designer/three_d_garden.scss @@ -1,6 +1,7 @@ @use "../variables" as *; @use "sass:color"; +.three-d-garden-loading-container, .promo-loading-container { display: grid; align-content: center; @@ -13,10 +14,17 @@ z-index: -9999999; } +.three-d-garden-loading-container { + z-index: unset; + width: 100vw; + transform: translateX(22.5rem); +} + .promo-loading-image { height: 100vh; } +.three-d-garden-loading-text, .promo-loading-text { font-family: 'Inknut Antiqua', sans-serif; font-size: 2rem; diff --git a/frontend/css/panels/help.scss b/frontend/css/panels/help.scss index b7ac6caad3..e5b8fd672f 100644 --- a/frontend/css/panels/help.scss +++ b/frontend/css/panels/help.scss @@ -104,7 +104,7 @@ body:has(.app.dark) { .feedback { p { - color: $dark_gray !important; + color: var(--text-color) !important; margin-bottom: 1rem !important; font-weight: normal !important; font-style: italic; diff --git a/frontend/css/panels/logs.scss b/frontend/css/panels/logs.scss index 13ccdb5744..b0a4d2f664 100644 --- a/frontend/css/panels/logs.scss +++ b/frontend/css/panels/logs.scss @@ -74,7 +74,7 @@ border-right: 1.5px $light_gray solid; .line-label { position: absolute; - background: $white; + background: var(--main-bg); font-family: monospace; } } diff --git a/frontend/css/panels/setup_wizard.scss b/frontend/css/panels/setup_wizard.scss index 006faff2ec..a3da1d964b 100644 --- a/frontend/css/panels/setup_wizard.scss +++ b/frontend/css/panels/setup_wizard.scss @@ -111,13 +111,14 @@ white-space: pre-wrap; line-height: 1.8rem; text-align: justify; + text-overflow: unset; } } } .wizard-step-q-and-a { background-color: $dark_gray; box-shadow: 0 0 1rem $translucent; - border-radius: 1rem; + border-radius: 0.5rem; display: grid; gap: 1rem; justify-content: center; @@ -218,13 +219,13 @@ } } .troubleshooting-tip { - border: 2px solid $medium_light_gray; + border: 2px solid var(--border-color); border-radius: 5px; padding: 1rem; cursor: pointer; - background: $panel_light_gray; + background: transparent; &:hover { - background: $white; + background: var(--secondary-bg); box-shadow: 0 1px 5px 0 $translucent; } p { @@ -236,8 +237,7 @@ text-decoration: underline; } &.selected { - border-color: $dark_gray; - background: $white; + background: var(--secondary-bg); p:first-of-type { font-weight: bold; } diff --git a/frontend/demo/__tests__/demo_iframe_test.tsx b/frontend/demo/__tests__/demo_iframe_test.tsx index 346363e9e4..0a6c7e919e 100644 --- a/frontend/demo/__tests__/demo_iframe_test.tsx +++ b/frontend/demo/__tests__/demo_iframe_test.tsx @@ -45,7 +45,7 @@ describe("", () => { it("changes model", () => { const wrapper = shallow(); - expect(wrapper.state().productLine).toEqual("genesis_1.7"); + expect(wrapper.state().productLine).toEqual("genesis_1.8"); wrapper.find("FBSelect").simulate("change", { value: "express_1.2" }); expect(wrapper.state().productLine).toEqual("express_1.2"); }); diff --git a/frontend/demo/demo_iframe.tsx b/frontend/demo/demo_iframe.tsx index e11e362391..9d370c64fd 100644 --- a/frontend/demo/demo_iframe.tsx +++ b/frontend/demo/demo_iframe.tsx @@ -31,7 +31,7 @@ export class DemoIframe extends React.Component<{}, State> { state: State = { error: undefined, stage: t("DEMO THE APP"), - productLine: "genesis_1.7", + productLine: "genesis_1.8", }; setError = (error?: Error) => this.setState({ error }); diff --git a/frontend/devices/interfaces.ts b/frontend/devices/interfaces.ts index 44ef7d0d06..ad89422bab 100644 --- a/frontend/devices/interfaces.ts +++ b/frontend/devices/interfaces.ts @@ -41,6 +41,7 @@ export enum Feature { farmduino_k15 = "farmduino_k15", farmduino_k16 = "farmduino_k16", farmduino_k17 = "farmduino_k17", + farmduino_k18 = "farmduino_k18", firmware_restart = "firmware_restart", flash_firmware = "flash_firmware", groups = "groups", diff --git a/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx b/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx index 511adb4a8b..5a71d91e3c 100644 --- a/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx +++ b/frontend/farm_designer/__tests__/three_d_garden_map_test.tsx @@ -12,6 +12,7 @@ import { render } from "@testing-library/react"; import { ThreeDGarden } from "../../three_d_garden"; import { clone } from "lodash"; import { INITIAL } from "../../three_d_garden/config"; +import { FirmwareHardware } from "farmbot"; describe("", () => { const fakeProps = (): ThreeDGardenMapProps => ({ @@ -38,7 +39,7 @@ describe("", () => { const expectedConfig = clone(INITIAL); expectedConfig.bedWallThickness = 1; expectedConfig.bedHeight = 1; - expectedConfig.bedLengthOuter = 3160; + expectedConfig.bedLengthOuter = 3280; expectedConfig.bedWidthOuter = 1660; expectedConfig.botSizeX = 3000; expectedConfig.botSizeY = 1500; @@ -92,4 +93,20 @@ describe("", () => { weeds: [], }, {}); }); + + it.each<[FirmwareHardware, string]>([ + ["farmduino", "v1.7"], + ["farmduino_k17", "v1.7"], + ["farmduino_k18", "v1.8"], + ])("converts props: kitVersion", (firmwareHardware, kitVersion) => { + const p = fakeProps(); + p.sourceFbosConfig = () => ({ value: firmwareHardware, consistent: true }); + render(); + expect(ThreeDGarden).toHaveBeenCalledWith({ + config: expect.objectContaining({ kitVersion }), + addPlantProps: expect.any(Object), + mapPoints: [], + weeds: [], + }, {}); + }); }); diff --git a/frontend/farm_designer/three_d_garden_map.tsx b/frontend/farm_designer/three_d_garden_map.tsx index 0da10ff24d..2552158ba1 100644 --- a/frontend/farm_designer/three_d_garden_map.tsx +++ b/frontend/farm_designer/three_d_garden_map.tsx @@ -39,10 +39,15 @@ export const ThreeDGardenMap = (props: ThreeDGardenMapProps) => { config.botSizeX = gridSize.x; config.botSizeY = gridSize.y; config.bedWidthOuter = gridSize.y + 160; - config.bedLengthOuter = gridSize.x + 160; + config.bedLengthOuter = gridSize.x + 280; config.zoomBeacons = false; config.trail = !!props.getWebAppConfigValue(BooleanSetting.display_trail); + config.kitVersion = + props.sourceFbosConfig("firmware_hardware").value == "farmduino_k18" + ? "v1.8" + : "v1.7"; + config.negativeZ = props.negativeZ; config.x = props.botPosition.x || 0; diff --git a/frontend/internal_urls.ts b/frontend/internal_urls.ts index df9f00248e..6305f992b8 100644 --- a/frontend/internal_urls.ts +++ b/frontend/internal_urls.ts @@ -107,6 +107,7 @@ export namespace FilePath { export const emptyState = (bug: string) => `${images("empty_state")}/${bug}.png`; export const DEFAULT_ICON = image("generic-plant"); export const DEFAULT_WEED_ICON = image("generic-weed"); + export const THREE_D_GARDEN_LOADING = "/promo_loading_image.avif"; } export enum Icon { diff --git a/frontend/messages/__tests__/cards_test.tsx b/frontend/messages/__tests__/cards_test.tsx index 6489b93e02..c9decf9e24 100644 --- a/frontend/messages/__tests__/cards_test.tsx +++ b/frontend/messages/__tests__/cards_test.tsx @@ -51,6 +51,10 @@ import moment from "moment"; API.setBaseUrl(""); describe("", () => { + beforeEach(() => { + mockFeatureBoolean = false; + }); + const fakeProps = (): AlertCardProps => ({ alert: { created_at: 123, @@ -105,6 +109,7 @@ describe("", () => { }); it("renders firmware card with new boards", () => { + mockFeatureBoolean = true; const p = fakeProps(); p.alert.problem_tag = "farmbot_os.firmware.missing"; p.alert.created_at = 1555555555; @@ -276,7 +281,7 @@ describe("SEED_DATA_OPTIONS()", () => { it("returns more options", () => { mockFeatureBoolean = true; - expect(SEED_DATA_OPTIONS().length).toEqual(17); + expect(SEED_DATA_OPTIONS().length).toEqual(19); }); }); diff --git a/frontend/messages/cards.tsx b/frontend/messages/cards.tsx index 2cf913befc..9657858e63 100644 --- a/frontend/messages/cards.tsx +++ b/frontend/messages/cards.tsx @@ -167,6 +167,12 @@ const FirmwareChoiceTable = () => + {shouldDisplayFeature(Feature.farmduino_k18) && + + {"Genesis v1.8"} + {"Farmduino"} + {FIRMWARE_CHOICES_DDI["farmduino_k18"].label} + } {"Genesis v1.7"} {"Farmduino"} @@ -251,25 +257,31 @@ const FirmwareMissing = (props: FirmwareMissingProps) => ; export const SEED_DATA_OPTIONS = (displayAll = false): DropDownItem[] => [ + ...((shouldDisplayFeature(Feature.farmduino_k18) || displayAll) + ? [{ label: "Genesis v1.8", value: "genesis_1.8" }] + : []), + ...((shouldDisplayFeature(Feature.farmduino_k18) || displayAll) + ? [{ label: "Genesis v1.8 XL", value: "genesis_xl_1.8" }] + : []), { label: "Genesis v1.7", value: "genesis_1.7" }, + { label: "Genesis v1.7 XL", value: "genesis_xl_1.7" }, { label: "Genesis v1.6", value: "genesis_1.6" }, + { label: "Genesis v1.6 XL", value: "genesis_xl_1.6" }, { label: "Genesis v1.5", value: "genesis_1.5" }, + { label: "Genesis v1.5 XL", value: "genesis_xl_1.5" }, { label: "Genesis v1.4", value: "genesis_1.4" }, + { label: "Genesis v1.4 XL", value: "genesis_xl_1.4" }, { label: "Genesis v1.3", value: "genesis_1.3" }, { label: "Genesis v1.2", value: "genesis_1.2" }, - { label: "Genesis v1.7 XL", value: "genesis_xl_1.7" }, - { label: "Genesis v1.6 XL", value: "genesis_xl_1.6" }, - { label: "Genesis v1.5 XL", value: "genesis_xl_1.5" }, - { label: "Genesis v1.4 XL", value: "genesis_xl_1.4" }, ...((shouldDisplayFeature(Feature.express_k12) || displayAll) ? [{ label: "Express v1.2", value: "express_1.2" }] : []), - { label: "Express v1.1", value: "express_1.1" }, - { label: "Express v1.0", value: "express_1.0" }, ...((shouldDisplayFeature(Feature.express_k12) || displayAll) ? [{ label: "Express v1.2 XL", value: "express_xl_1.2" }] : []), + { label: "Express v1.1", value: "express_1.1" }, { label: "Express v1.1 XL", value: "express_xl_1.1" }, + { label: "Express v1.0", value: "express_1.0" }, { label: "Express v1.0 XL", value: "express_xl_1.0" }, { label: "Custom Bot", value: "none" }, ]; diff --git a/frontend/os_download/__tests__/content_test.tsx b/frontend/os_download/__tests__/content_test.tsx index fffb3febdf..f874a3786f 100644 --- a/frontend/os_download/__tests__/content_test.tsx +++ b/frontend/os_download/__tests__/content_test.tsx @@ -4,9 +4,11 @@ jest.mock("../../screen_size", () => ({ })); import React from "react"; -import { mount } from "enzyme"; +import { render, screen, fireEvent } from "@testing-library/react"; import { OsDownloadPage } from "../content"; -import { clickButton } from "../../__test_support__/helpers"; + +const text = (computer: string) => + `Your FarmBot's internal computer is the Raspberry Pi ${computer}`; describe("", () => { it("renders", () => { @@ -16,86 +18,96 @@ describe("", () => { globalConfig.rpi_release_url = "fake rpi img url"; globalConfig.rpi_release_tag = "1.0.0"; - const wrapper = mount(); - clickButton(wrapper, 2, "show all download links"); + render(); + const btn = screen.getByText("Show all download links"); + fireEvent.click(btn); - const rpi3Link = wrapper.find("a").first(); - expect(rpi3Link.text()).toEqual("DOWNLOAD v1.0.1"); - expect(rpi3Link.props().href).toEqual("fake rpi4 img url"); + const rpi3Link = screen.getAllByRole("link")[0]; + expect(rpi3Link.textContent).toEqual("DOWNLOAD v1.0.1"); + expect(rpi3Link.getAttribute("href")).toEqual("fake rpi4 img url"); - const rpiLink = wrapper.find("a").last(); - expect(rpiLink.text()).toEqual("DOWNLOAD v1.0.0"); - expect(rpiLink.props().href).toEqual("fake rpi img url"); + const rpiLink = screen.getAllByRole("link")[1]; + expect(rpiLink.textContent).toEqual("DOWNLOAD v1.0.0"); + expect(rpiLink.getAttribute("href")).toEqual("fake rpi img url"); }); it("renders on small screens", () => { mockIsMobile = true; - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("download"); + const { container } = render(); + expect(container).toContainHTML("download"); }); it("renders on large screens", () => { mockIsMobile = false; - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("download"); + const { container } = render(); + expect(container).toContainHTML("download"); }); it("toggles the wizard", () => { - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("show"); - expect(wrapper.text().toLowerCase()).not.toContain("return"); - clickButton(wrapper, 2, "show all download links"); - expect(wrapper.text().toLowerCase()).not.toContain("show"); - expect(wrapper.text().toLowerCase()).toContain("return"); - clickButton(wrapper, 0, "return to the wizard"); - expect(wrapper.text().toLowerCase()).toContain("show"); - expect(wrapper.text().toLowerCase()).not.toContain("return"); + const ALL = "Show all download links"; + const RETURN = "Return to the wizard"; + render(); + expect(screen.getByText(ALL)).toBeInTheDocument(); + expect(screen.queryByText(RETURN)).not.toBeInTheDocument(); + fireEvent.click(screen.getByText(ALL)); + expect(screen.queryByText(ALL)).not.toBeInTheDocument(); + expect(screen.getByText(RETURN)).toBeInTheDocument(); + fireEvent.click(screen.getByText(RETURN)); + expect(screen.getByText(ALL)).toBeInTheDocument(); + expect(screen.queryByText(RETURN)).not.toBeInTheDocument(); }); it("runs the wizard: express", () => { - const wrapper = mount(); - clickButton(wrapper, 1, "express", { partial_match: true }); - clickButton(wrapper, 1, "express v1.0"); - expect(wrapper.text().toLowerCase()).toContain("zero"); + render(); + fireEvent.click(screen.getByText("Express")); + fireEvent.click(screen.getByText("Express v1.0")); + expect(screen.getByText(text("Zero W"))).toBeInTheDocument(); }); it("runs the wizard: genesis", () => { - const wrapper = mount(); - clickButton(wrapper, 0, "genesis", { partial_match: true }); - clickButton(wrapper, 5, "genesis v1.2"); - expect(wrapper.text().toLowerCase()).toContain("pi 3"); + render(); + fireEvent.click(screen.getByText("Genesis")); + fireEvent.click(screen.getByText("Genesis v1.2")); + expect(screen.getByText(text("3"))).toBeInTheDocument(); }); it("runs the wizard: genesis v1.6.0", () => { - const wrapper = mount(); - clickButton(wrapper, 0, "genesis", { partial_match: true }); - clickButton(wrapper, 1, "genesis v1.6"); - clickButton(wrapper, 0, "black"); - clickButton(wrapper, 0, "raspberry pi model 3"); - expect(wrapper.text().toLowerCase()).toContain("pi 3"); + render(); + fireEvent.click(screen.getByText("Genesis")); + fireEvent.click(screen.getByText("Genesis v1.6")); + fireEvent.click(screen.getByText("Black")); + fireEvent.click(screen.getByText("Raspberry Pi Model 3")); + expect(screen.getByText(text("3"))).toBeInTheDocument(); }); it("runs the wizard: genesis v1.6.1 & some v1.6.2", () => { - const wrapper = mount(); - clickButton(wrapper, 0, "genesis", { partial_match: true }); - clickButton(wrapper, 1, "genesis v1.6"); - clickButton(wrapper, 1, "white"); - expect(wrapper.text().toLowerCase()).toContain("pi 4"); + render(); + fireEvent.click(screen.getByText("Genesis")); + fireEvent.click(screen.getByText("Genesis v1.6")); + fireEvent.click(screen.getByText("White")); + expect(screen.getByText(text("4"))).toBeInTheDocument(); }); it("runs the wizard: genesis other v1.6.2", () => { - const wrapper = mount(); - clickButton(wrapper, 0, "genesis", { partial_match: true }); - clickButton(wrapper, 1, "genesis v1.6"); - clickButton(wrapper, 0, "black"); - clickButton(wrapper, 1, "raspberry pi model 4"); - expect(wrapper.text().toLowerCase()).toContain("pi 4"); + render(); + fireEvent.click(screen.getByText("Genesis")); + fireEvent.click(screen.getByText("Genesis v1.6")); + fireEvent.click(screen.getByText("Black")); + fireEvent.click(screen.getByText("Raspberry Pi Model 4")); + expect(screen.getByText(text("4"))).toBeInTheDocument(); }); it("runs the wizard: genesis v1.7", () => { - const wrapper = mount(); - clickButton(wrapper, 0, "genesis", { partial_match: true }); - clickButton(wrapper, 0, "genesis v1.7"); - expect(wrapper.text().toLowerCase()).toContain("pi 4"); + render(); + fireEvent.click(screen.getByText("Genesis")); + fireEvent.click(screen.getByText("Genesis v1.7")); + expect(screen.getByText(text("4"))).toBeInTheDocument(); + }); + + it("runs the wizard: genesis v1.8", () => { + render(); + fireEvent.click(screen.getByText("Genesis")); + fireEvent.click(screen.getByText("Genesis v1.8")); + expect(screen.getByText(text("4"))).toBeInTheDocument(); }); }); diff --git a/frontend/os_download/content.tsx b/frontend/os_download/content.tsx index da32ebb7c2..b67a27b147 100644 --- a/frontend/os_download/content.tsx +++ b/frontend/os_download/content.tsx @@ -47,6 +47,8 @@ const PLATFORM_DATA = (): PlatformContent[] => [ imageUrl: RPI4().imageUrl, releaseTag: RPI4().releaseTag, kits: [ + "Genesis v1.8", + "Genesis XL v1.8", "Genesis v1.7", "Genesis XL v1.7", "Genesis v1.6.2 (white cable or 2 HDMI ports)", @@ -141,6 +143,7 @@ enum Version { "v1.5" = "v1.5", "v1.6" = "v1.6", "v1.7" = "v1.7", + "v1.8" = "v1.8", } const VERSIONS = () => ({ @@ -150,6 +153,7 @@ const VERSIONS = () => ({ Version["v1.0"], ], [Model.Genesis]: [ + Version["v1.8"], Version["v1.7"], Version["v1.6"], Version["v1.5"], @@ -237,6 +241,9 @@ const DOWNLOADS = (): Downloads => ({ [Version["v1.7"]]: { [Run.first]: RPI4(), }, + [Version["v1.8"]]: { + [Run.first]: RPI4(), + }, } }); diff --git a/frontend/redux/__tests__/refilter_logs_middleware_test.ts b/frontend/redux/__tests__/refilter_logs_middleware_test.ts index a072deac2c..e826bc4c93 100644 --- a/frontend/redux/__tests__/refilter_logs_middleware_test.ts +++ b/frontend/redux/__tests__/refilter_logs_middleware_test.ts @@ -1,18 +1,52 @@ jest.mock("../refresh_logs", () => ({ throttledLogRefresh: jest.fn() })); + import { refilterLogsMiddleware } from "../refilter_logs_middleware"; import { throttledLogRefresh } from "../refresh_logs"; import { Actions } from "../../constants"; -import { ReduxAction } from "../interfaces"; import { Store } from "redux"; import { Everything } from "../../interfaces"; describe("refilterLogsMiddleware.fn()", () => { - it("dispatches when required", () => { - const dispatch = jest.fn(); - const fn = refilterLogsMiddleware.fn({} as Store)(dispatch); - fn({ type: "any", payload: {} } as unknown as ReduxAction<{}>); + const dispatch = jest.fn(); + const fn = refilterLogsMiddleware.fn({} as Store)(dispatch); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("ignores unrelated and incomplete actions", () => { + fn({ type: Actions.SET_PANEL_OPEN, payload: false as unknown as object }); + fn({ type: Actions.SAVE_RESOURCE_OK, payload: { kind: "Point" } }); + expect(throttledLogRefresh).not.toHaveBeenCalled(); + }); + + it("ignores non log filter WebAppConfig changes", () => { + fn({ + type: Actions.SAVE_RESOURCE_OK, + payload: { kind: "WebAppConfig", body: { show_plants: true } }, + }); + expect(throttledLogRefresh).not.toHaveBeenCalled(); + }); + + it("triggers refresh when log filter changes", () => { + fn({ + type: Actions.SAVE_RESOURCE_OK, + payload: { kind: "WebAppConfig", body: { success_log: 3 } }, + }); + expect(throttledLogRefresh).toHaveBeenCalledTimes(1); + }); + + it("does not trigger refresh if log filter value is unchanged", () => { + fn({ + type: Actions.SAVE_RESOURCE_OK, + payload: { kind: "WebAppConfig", body: { info_log: 3 } }, + }); + expect(throttledLogRefresh).toHaveBeenCalledTimes(1); + jest.clearAllMocks(); + fn({ + type: Actions.SAVE_RESOURCE_OK, + payload: { kind: "WebAppConfig", body: { info_log: 3 } }, + }); expect(throttledLogRefresh).not.toHaveBeenCalled(); - fn({ type: Actions.SAVE_RESOURCE_OK, payload: { kind: "WebAppConfig" } }); - expect(throttledLogRefresh).toHaveBeenCalled(); }); }); diff --git a/frontend/redux/refilter_logs_middleware.ts b/frontend/redux/refilter_logs_middleware.ts index cb32e26597..ccac7d45a0 100644 --- a/frontend/redux/refilter_logs_middleware.ts +++ b/frontend/redux/refilter_logs_middleware.ts @@ -1,21 +1,51 @@ import { Actions } from "../constants"; import { Middleware } from "redux"; import { MiddlewareConfig } from "./middlewares"; -import { ResourceName } from "farmbot"; +import { ResourceName, TaggedResource } from "farmbot"; import { throttledLogRefresh } from "./refresh_logs"; const WEB_APP_CONFIG: ResourceName = "WebAppConfig"; -/** - * Middleware function that listens for changes on the `WebAppConfig` resource. - * If the resource does change, it will trigger a throttled refresh of all log - * resources, downloading the filtered log list as required from the API. */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const fn: Middleware = () => (dispatch) => (action: any) => { - const needsRefresh = action?.payload?.kind === WEB_APP_CONFIG - && action.type === Actions.SAVE_RESOURCE_OK; +const LOG_RELATED_FIELDS = [ + "success_log", "busy_log", "warn_log", "error_log", + "info_log", "fun_log", "debug_log", "assertion_log", +] as const; - needsRefresh && throttledLogRefresh(dispatch); +// Cache the last seen values of each log related field +export type LogField = typeof LOG_RELATED_FIELDS[number]; +const cache: Partial> = {}; + +interface ResourceAction { + type: Actions; + payload: TaggedResource; +} + +// Refresh logs only when a log related WebAppConfig is changed +export const fn: Middleware = () => (dispatch) => (action: ResourceAction) => { + const { type, payload } = action; + + // Only proceed for WebAppConfig save actions + if (type !== Actions.SAVE_RESOURCE_OK || payload.kind !== WEB_APP_CONFIG) { + return dispatch(action); + } + + const { body } = payload; + + // Check if the action contains a changed log related field + let changed = false; + LOG_RELATED_FIELDS.forEach(key => { + if (key in body) { + const newValue = body[key as keyof typeof body] as number; + if (cache[key] !== newValue) { + changed = true; + } + cache[key] = newValue; + } + }); + + if (changed) { + throttledLogRefresh(dispatch); + } return dispatch(action); }; diff --git a/frontend/session_keys.ts b/frontend/session_keys.ts index b0a683b422..e87b666d11 100644 --- a/frontend/session_keys.ts +++ b/frontend/session_keys.ts @@ -4,9 +4,7 @@ import { StringConfigKey as WebAppStringConfigKey, } from "farmbot/dist/resources/configs/web_app"; -type WebAppBooleanConfigKeyAll = WebAppBooleanConfigKey - | "three_d_garden" - | "dark_mode"; +type WebAppBooleanConfigKeyAll = WebAppBooleanConfigKey; type WebAppNumberConfigKeyAll = WebAppNumberConfigKey; type WebAppStringConfigKeyAll = WebAppStringConfigKey; @@ -51,7 +49,7 @@ export const BooleanSetting: BooleanSettings = { crop_images: "crop_images", clip_image_layer: "clip_image_layer", highlight_modified_settings: "highlight_modified_settings", - three_d_garden: "three_d_garden" as WebAppBooleanConfigKey, + three_d_garden: "three_d_garden", /** Sequence settings */ confirm_step_deletion: "confirm_step_deletion", @@ -72,7 +70,7 @@ export const BooleanSetting: BooleanSettings = { time_format_seconds: "time_format_seconds", disable_emergency_unlock_confirmation: "disable_emergency_unlock_confirmation", user_interface_read_only_mode: "user_interface_read_only_mode", - dark_mode: "dark_mode" as WebAppBooleanConfigKey, + dark_mode: "dark_mode", /** Farmware settings */ show_first_party_farmware: "show_first_party_farmware", diff --git a/frontend/settings/__tests__/default_values_test.ts b/frontend/settings/__tests__/default_values_test.ts index c9db697fed..07b60730a7 100644 --- a/frontend/settings/__tests__/default_values_test.ts +++ b/frontend/settings/__tests__/default_values_test.ts @@ -29,6 +29,7 @@ describe("getModifiedClassName()", () => { [BooleanSetting.hide_sensors, "farmduino_k15", ""], [BooleanSetting.hide_sensors, "farmduino_k16", ""], [BooleanSetting.hide_sensors, "farmduino_k17", ""], + [BooleanSetting.hide_sensors, "farmduino_k18", ""], [BooleanSetting.hide_sensors, "express_k10", "modified"], [BooleanSetting.hide_sensors, "express_k11", "modified"], [BooleanSetting.hide_sensors, "express_k12", "modified"], diff --git a/frontend/settings/default_values.ts b/frontend/settings/default_values.ts index 91aa18b86b..0f3f874c7d 100644 --- a/frontend/settings/default_values.ts +++ b/frontend/settings/default_values.ts @@ -87,8 +87,8 @@ const DEFAULT_WEB_APP_CONFIG_VALUES: Record = { view_celery_script: false, highlight_modified_settings: true, show_advanced_settings: false, - ["three_d_garden" as Key]: false, - ["dark_mode" as Key]: false, + three_d_garden: false, + dark_mode: true, }; const DEFAULT_EXPRESS_WEB_APP_CONFIG_VALUES = @@ -107,6 +107,7 @@ const getDefaultConfigValue = case "farmduino_k15": case "farmduino_k16": case "farmduino_k17": + case "farmduino_k18": return DEFAULT_GENESIS_WEB_APP_CONFIG_VALUES[key]; case "express_k10": case "express_k11": diff --git a/frontend/settings/fbos_settings/__tests__/rpi_model_test.tsx b/frontend/settings/fbos_settings/__tests__/rpi_model_test.tsx index e5f386bc3c..18f52a23a8 100644 --- a/frontend/settings/fbos_settings/__tests__/rpi_model_test.tsx +++ b/frontend/settings/fbos_settings/__tests__/rpi_model_test.tsx @@ -20,6 +20,7 @@ const TEST_CASES: TestCase[] = [ ["3", "rpi3", "arduino", "pi 3"], ["4", "rpi4", "farmduino_k16", "pi 4"], ["4", "rpi4", "farmduino_k17", "pi 4"], + ["4", "rpi4", "farmduino_k18", "pi 4"], ["01", "rpi", "express_k10", "zero w"], ["02", "rpi3", "express_k11", "zero 2 w"], ["02", "rpi3", "express_k12", "zero 2 w"], diff --git a/frontend/settings/fbos_settings/default_values.ts b/frontend/settings/fbos_settings/default_values.ts index a61b7b11fe..b7befdec60 100644 --- a/frontend/settings/fbos_settings/default_values.ts +++ b/frontend/settings/fbos_settings/default_values.ts @@ -46,6 +46,7 @@ export const getDefaultConfigValue = case "farmduino_k15": case "farmduino_k16": case "farmduino_k17": + case "farmduino_k18": return DEFAULT_GENESIS_FBOS_CONFIG_VALUES[key]; case "express_k10": case "express_k11": diff --git a/frontend/settings/fbos_settings/farmbot_os_row.tsx b/frontend/settings/fbos_settings/farmbot_os_row.tsx index d0c9a49daf..e74f051da2 100644 --- a/frontend/settings/fbos_settings/farmbot_os_row.tsx +++ b/frontend/settings/fbos_settings/farmbot_os_row.tsx @@ -38,6 +38,10 @@ export const FarmbotOsRow = (props: FarmbotOsRowProps) => { const [channel, setChannel] = React.useState( "" + props.sourceFbosConfig("update_channel").value); + const { dispatch, bot, sourceFbosConfig } = props; + const { controller_version, target } = bot.hardware.informational_settings; + const configChannel = "" + sourceFbosConfig("update_channel").value; + React.useEffect(() => { const { dispatch } = props; const { target } = props.bot.hardware.informational_settings; @@ -46,9 +50,6 @@ export const FarmbotOsRow = (props: FarmbotOsRowProps) => { }, []); React.useEffect(() => { - const { dispatch, bot, sourceFbosConfig } = props; - const { controller_version, target } = bot.hardware.informational_settings; - const configChannel = "" + sourceFbosConfig("update_channel").value; const versionChange = controller_version && version != controller_version; const channelChange = configChannel && channel != configChannel; if (versionChange || channelChange) { @@ -59,38 +60,11 @@ export const FarmbotOsRow = (props: FarmbotOsRowProps) => { if (versionChange) { removeToast("EOL"); } - }, [props, version, channel]); + }, [dispatch, controller_version, target, configChannel, version, channel]); - const Version = () => { - const { controller_version } = props.bot.hardware.informational_settings; - const version = controller_version || t("unknown (offline)"); - return - {t("Version {{ version }}", { version })} -

} - content={ - - } />; - }; - - const ReleaseNotes = () => { - const { osReleaseNotes, hardware } = props.bot; - const { controller_version } = hardware.informational_settings; - const { fbos_version } = props.device.body; - const version = controller_version || fbos_version; - const releaseNotes = getOsReleaseNotesForVersion(osReleaseNotes, version); - return
-

{releaseNotes.heading}

- - {releaseNotes.notes} - -
; - }; + const releaseNotes = getOsReleaseNotesForVersion( + props.bot.osReleaseNotes, + version || props.device.body.fbos_version); return ; diff --git a/frontend/settings/firmware/__tests__/board_type_test.tsx b/frontend/settings/firmware/__tests__/board_type_test.tsx index 1a94692ce5..d6a399ba55 100644 --- a/frontend/settings/firmware/__tests__/board_type_test.tsx +++ b/frontend/settings/firmware/__tests__/board_type_test.tsx @@ -98,5 +98,6 @@ describe("", () => { const selection = screen.getByRole("combobox"); fireEvent.click(selection); expect(screen.getByText("Farmduino (Express v1.2)")).toBeInTheDocument(); + expect(screen.getByText("Farmduino (Genesis v1.8)")).toBeInTheDocument(); }); }); diff --git a/frontend/settings/firmware/__tests__/firmware_hardware_support_test.ts b/frontend/settings/firmware/__tests__/firmware_hardware_support_test.ts index e930618019..9838382fe0 100644 --- a/frontend/settings/firmware/__tests__/firmware_hardware_support_test.ts +++ b/frontend/settings/firmware/__tests__/firmware_hardware_support_test.ts @@ -24,6 +24,10 @@ describe("boardType()", () => { expect(boardType("5.0.3.J")).toEqual("farmduino_k17"); }); + it("returns Farmduino k1.8", () => { + expect(boardType("5.0.3.K")).toEqual("farmduino_k18"); + }); + it("returns Farmduino Express k1.0", () => { expect(boardType("5.0.3.E")).toEqual("express_k10"); }); diff --git a/frontend/settings/firmware/board_type.tsx b/frontend/settings/firmware/board_type.tsx index 75fcc39478..ed8152681b 100644 --- a/frontend/settings/firmware/board_type.tsx +++ b/frontend/settings/firmware/board_type.tsx @@ -55,7 +55,7 @@ export const BoardType = (props: BoardTypeProps) => { warning(t(Content.FIRMWARE_UPGRADED), { title: t("Action may be required") }); dispatch(updateConfig({ firmware_hardware })); - }} />; + }} /> ; }; diff --git a/frontend/settings/firmware/firmware_hardware_support.ts b/frontend/settings/firmware/firmware_hardware_support.ts index cb196901e7..6b2a21f6bd 100644 --- a/frontend/settings/firmware/firmware_hardware_support.ts +++ b/frontend/settings/firmware/firmware_hardware_support.ts @@ -5,7 +5,8 @@ import { Feature } from "../../devices/interfaces"; export const isFwHardwareValue = (x?: unknown): x is FirmwareHardware => { const values: FirmwareHardware[] = [ "arduino", - "farmduino", "farmduino_k14", "farmduino_k15", "farmduino_k16", "farmduino_k17", + "farmduino", "farmduino_k14", "farmduino_k15", + "farmduino_k16", "farmduino_k17", "farmduino_k18", "express_k10", "express_k11", "express_k12", "none", ]; @@ -22,6 +23,7 @@ const ordered: FirmwareHardware[] = [ "farmduino_k15", "farmduino_k16", "farmduino_k17", + "farmduino_k18", "none", ]; @@ -128,6 +130,7 @@ const FIRMWARE_LOOKUP: { [id: string]: FirmwareHardware } = { H: "farmduino_k15", I: "farmduino_k16", J: "farmduino_k17", + K: "farmduino_k18", E: "express_k10", D: "express_k11", C: "express_k12", @@ -140,6 +143,7 @@ enum BoardLabels { farmduino_k15 = "Farmduino (Genesis v1.5)", farmduino_k16 = "Farmduino (Genesis v1.6)", farmduino_k17 = "Farmduino (Genesis v1.7)", + farmduino_k18 = "Farmduino (Genesis v1.8)", express_k10 = "Farmduino (Express v1.0)", express_k11 = "Farmduino (Express v1.1)", express_k12 = "Farmduino (Express v1.2)", @@ -153,6 +157,7 @@ enum KitLabels { farmduino_k15 = "Genesis v1.5", farmduino_k16 = "Genesis v1.6", farmduino_k17 = "Genesis v1.7", + farmduino_k18 = "Genesis v1.8", express_k10 = "Express v1.0", express_k11 = "Express v1.1", express_k12 = "Express v1.2", @@ -167,6 +172,7 @@ const KIT_LOOKUP = { farmduino_k15: KitLabels.farmduino_k15, farmduino_k16: KitLabels.farmduino_k16, farmduino_k17: KitLabels.farmduino_k17, + farmduino_k18: KitLabels.farmduino_k18, express_k10: KitLabels.express_k10, express_k11: KitLabels.express_k11, express_k12: KitLabels.express_k12, @@ -180,12 +186,14 @@ const FARMDUINO_K14 = { label: BoardLabels.farmduino_k14, value: "farmduino_k14" const FARMDUINO_K15 = { label: BoardLabels.farmduino_k15, value: "farmduino_k15" }; const FARMDUINO_K16 = { label: BoardLabels.farmduino_k16, value: "farmduino_k16" }; const FARMDUINO_K17 = { label: BoardLabels.farmduino_k17, value: "farmduino_k17" }; +const FARMDUINO_K18 = { label: BoardLabels.farmduino_k18, value: "farmduino_k18" }; const EXPRESS_K10 = { label: BoardLabels.express_k10, value: "express_k10" }; const EXPRESS_K11 = { label: BoardLabels.express_k11, value: "express_k11" }; const EXPRESS_K12 = { label: BoardLabels.express_k12, value: "express_k12" }; const NONE = { label: BoardLabels.none, value: "none" }; export const FIRMWARE_CHOICES_DDI = { + [FARMDUINO_K18.value]: FARMDUINO_K18, [FARMDUINO_K17.value]: FARMDUINO_K17, [FARMDUINO_K16.value]: FARMDUINO_K16, [FARMDUINO_K15.value]: FARMDUINO_K15, @@ -199,6 +207,7 @@ export const FIRMWARE_CHOICES_DDI = { }; export const getFirmwareChoices = () => ([ + ...(shouldDisplayFeature(Feature.farmduino_k18) ? [FARMDUINO_K18] : []), FARMDUINO_K17, FARMDUINO_K16, FARMDUINO_K15, diff --git a/frontend/settings/hardware_settings/default_values.ts b/frontend/settings/hardware_settings/default_values.ts index 80c7527731..12df6305c5 100644 --- a/frontend/settings/hardware_settings/default_values.ts +++ b/frontend/settings/hardware_settings/default_values.ts @@ -156,6 +156,7 @@ export const getDefaultFwConfigValue = case "farmduino_k15": case "farmduino_k16": case "farmduino_k17": + case "farmduino_k18": return DEFAULT_GENESIS_FIRMWARE_CONFIG_VALUES[key]; case "express_k10": case "express_k11": diff --git a/frontend/three_d_garden/__tests__/config_overlays_test.tsx b/frontend/three_d_garden/__tests__/config_overlays_test.tsx index f3a4a47091..5277f494f3 100644 --- a/frontend/three_d_garden/__tests__/config_overlays_test.tsx +++ b/frontend/three_d_garden/__tests__/config_overlays_test.tsx @@ -115,7 +115,7 @@ describe("", () => { it("changes value: radio", () => { const p = fakeProps(); const wrapper = mount(); - wrapper.find({ type: "radio" }).first().simulate("change", + wrapper.find({ type: "radio" }).at(2).simulate("change", { target: { value: "Jr" } }); expect(p.setConfig).toHaveBeenCalledWith({ ...p.config, diff --git a/frontend/three_d_garden/bed/objects/__tests__/packaging_test.tsx b/frontend/three_d_garden/bed/objects/__tests__/packaging_test.tsx index c49b7fb9a8..73ddc81332 100644 --- a/frontend/three_d_garden/bed/objects/__tests__/packaging_test.tsx +++ b/frontend/three_d_garden/bed/objects/__tests__/packaging_test.tsx @@ -12,10 +12,10 @@ describe("", () => { it("renders", () => { const p = fakeProps(); p.config.packaging = true; - p.config.kitVersion = "v1.7"; + p.config.kitVersion = "v1.n"; const wrapper = mount(); expect(wrapper.html()).toContain("packaging"); - expect(wrapper.html()).toContain("100"); + expect(wrapper.html()).not.toContain("100"); expect(wrapper.html()).not.toContain("170"); }); diff --git a/frontend/three_d_garden/bed/objects/packaging.tsx b/frontend/three_d_garden/bed/objects/packaging.tsx index e601b3e2e1..27adf6a982 100644 --- a/frontend/three_d_garden/bed/objects/packaging.tsx +++ b/frontend/three_d_garden/bed/objects/packaging.tsx @@ -5,6 +5,26 @@ import { Config } from "../../config"; import { Group, MeshPhongMaterial } from "../../components"; import { Text } from "../../elements"; +const mainCartonHeightFunc = (kitVersion: string): number => { + switch (kitVersion) { + case "v1.7": + return 220; + case "v1.8": + default: + return 245; + } +}; + +const extrusionKitVisibility = (kitVersion: string) => { + switch (kitVersion) { + case "v1.7": + return true; + case "v1.8": + default: + return false; + } +}; + export interface PackagingProps { config: Config; } @@ -14,7 +34,7 @@ export const Packaging = (props: PackagingProps) => { const isXL = config.sizePreset == "Genesis XL"; const mainCartonLength = 1060; const mainCartonWidth = 420; - const mainCartonHeight = config.kitVersion == "v1.7" ? 220 : 245; + const mainCartonHeight = mainCartonHeightFunc(config.kitVersion); const extrusionKitLength = 1540; const extrusionKitWidth = isXL ? 170 : 100; const extrusionKitHeight = 60; @@ -31,6 +51,7 @@ export const Packaging = (props: PackagingProps) => { const boxColor = "#bf8b59"; const strapColor = "#434343"; const edgeProtectorColor = "#9d6c40"; + return { )} ", () => { p.config.sizePreset = "Genesis"; p.config.tracks = true; p.config.trail = true; + p.config.kitVersion = "v1.n"; const wrapper = mount(); expect(wrapper.html()).toContain("bot"); expect(wrapper.html()).toContain("water-tube"); diff --git a/frontend/three_d_garden/bot/bot.tsx b/frontend/three_d_garden/bot/bot.tsx index 18145f10e0..e02832d59e 100644 --- a/frontend/three_d_garden/bot/bot.tsx +++ b/frontend/three_d_garden/bot/bot.tsx @@ -24,10 +24,15 @@ import { PowerSupply } from "./power_supply"; import { Group, Mesh, MeshPhongMaterial } from "../components"; import { ElectronicsBox, Bounds, Tools, Solenoid, XAxisWaterTube, + CableCarrierX, + CableCarrierVertical, + CableCarrierZ, + CableCarrierY, + CableCarrierHorizontal, } from "./components"; import { SlotWithTool } from "../../resources/interfaces"; -const extrusionWidth = 20; +export const extrusionWidth = 20; const utmRadius = 35; export const utmHeight = 35; const xTrackPadding = 280; @@ -53,14 +58,6 @@ type UTM = GLTF & { nodes: { [PartName.utm]: THREE.Mesh }; materials: { PaletteMaterial001: THREE.MeshStandardMaterial }; } -type CCHorizontal = GLTF & { - nodes: { [PartName.ccHorizontal]: THREE.Mesh }; - materials: never; -} -type CCVertical = GLTF & { - nodes: { [PartName.ccVertical]: THREE.Mesh }; - materials: never; -} type HousingVertical = GLTF & { nodes: { [PartName.housingVertical]: THREE.Mesh }; materials: never; @@ -84,35 +81,6 @@ type XAxisCCMount = GLTF & { Object.values(ASSETS.models).map(model => useGLTF.preload(model, LIB_DIR)); -const ccPath = - (axisLength: number, y: number, curveDia: number, isX?: boolean) => { - const lowerLength = (y + axisLength + 180) / 2; - const upperLength = lowerLength - y; - const outerRadius = curveDia / 2; - const height = isX ? 15 : 20; - const innerRadius = outerRadius - height; - - const path = new Shape(); - path.moveTo(y + 20, 0); - path.lineTo(y + upperLength, 0); - path.arc(0, outerRadius, outerRadius, -Math.PI / 2, Math.PI / 2); - path.lineTo(0, curveDia); - path.lineTo(0, curveDia - 5); - path.lineTo(20, curveDia - height); - path.lineTo(lowerLength, curveDia - height); - path.arc(0, -innerRadius, innerRadius, Math.PI / 2, -Math.PI / 2, true); - if (isX) { - path.lineTo(y + 20, height - 1); - path.lineTo(y, 5); - path.lineTo(y, 0); - } else { - path.lineTo(y, height - 1); - path.lineTo(y, height - 5); - } - path.lineTo(y + 20, 0); - return path; - }; - export interface FarmbotModelProps { config: Config; activeFocus: string; @@ -126,8 +94,7 @@ export const Bot = (props: FarmbotModelProps) => { const { x, y, z, botSizeX, botSizeY, botSizeZ, beamLength, trail, laser, soilHeight, bedXOffset, bedYOffset, bedLengthOuter, bedWidthOuter, tracks, - columnLength, zAxisLength, zGantryOffset, bedHeight, - cableCarriers, + columnLength, zAxisLength, zGantryOffset, } = props.config; const zZero = zZeroFunc(config); const zDir = zDirFunc(config); @@ -141,8 +108,6 @@ export const Bot = (props: FarmbotModelProps) => { const beltClip = useGLTF(ASSETS.models.beltClip, LIB_DIR) as BeltClip; const zStop = useGLTF(ASSETS.models.zStop, LIB_DIR) as ZStop; const utm = useGLTF(ASSETS.models.utm, LIB_DIR) as UTM; - const ccHorizontal = useGLTF(ASSETS.models.ccHorizontal, LIB_DIR) as CCHorizontal; - const ccVertical = useGLTF(ASSETS.models.ccVertical, LIB_DIR) as CCVertical; const housingVertical = useGLTF( ASSETS.models.housingVertical, LIB_DIR) as HousingVertical; const horizontalMotorHousing = useGLTF( @@ -225,7 +190,53 @@ export const Bot = (props: FarmbotModelProps) => { return path; }; const distanceToSoil = soilHeight - zDir * z; - const bedCCSupportHeight = Math.min(150, bedHeight / 2); + + const airTubeEndPosition = (kitVersion: string): [number, number, number] => { + switch (kitVersion) { + case "v1.7": + return [ + threeSpace(x + 80, bedLengthOuter) + bedXOffset, + threeSpace(y + 100, bedWidthOuter) + bedYOffset, + zZero - zDir * z + 245, + ]; + case "v1.8": + default: + return [ + threeSpace(x + 35, bedLengthOuter) + bedXOffset, + threeSpace(y, bedWidthOuter) + bedYOffset, + zZero - zDir * z + 245, + ]; + } + }; + + const vacuumPumpCoverRotation = (kitVersion: string): [number, number, number] => { + switch (kitVersion) { + case "v1.7": + return [0, 0, Math.PI / 2]; + case "v1.8": + default: + return [0, 0, -Math.PI / 2]; + } + }; + + const vacuumPumpCoverPosition = (kitVersion: string): [number, number, number] => { + switch (kitVersion) { + case "v1.7": + return [ + threeSpace(x + 12, bedLengthOuter) + bedXOffset, + threeSpace(y + 55, bedWidthOuter) + bedYOffset, + zZero - zDir * z + 490, + ]; + case "v1.8": + default: + return [ + threeSpace(x + 2, bedLengthOuter) + bedXOffset, + threeSpace(y + 110, bedWidthOuter) + bedYOffset, + zZero + columnLength + 25, + ]; + } + }; + return {[0 - extrusionWidth, bedWidthOuter].map((y, index) => { @@ -338,7 +349,7 @@ export const Bot = (props: FarmbotModelProps) => { { geometry={xAxisCCMount.nodes[PartName.xAxisCCMount].geometry}> - - - + { zZero - zDir * z + zAxisLength / 2, ]} rotation={[Math.PI / 2, 0, 0]} /> - - {config.kitVersion == "v1.7" - ? ( - range((zAxisLength - 350) / 200).map((i) => ( - - - - )) - ) - : ( - { - const shape = new THREE.Shape(); - shape.moveTo(0, 0); - shape.lineTo(0, 20); - shape.lineTo(15, 20); - shape.lineTo(20, 1.5); - shape.lineTo(28.5, 1.5); - shape.lineTo(28.5, -61); - shape.lineTo(24, -63); - shape.lineTo(24, -61.5); - shape.lineTo(27, -60); - shape.lineTo(27, 0); - shape.lineTo(0, 0); - return shape; - })(), - { - depth: zAxisLength - 350, - bevelEnabled: false, - }, - )}> - - - )} - - - - + + { ], [0, 0, 100], [0, 0, -200], - config.kitVersion == "v1.7" - ? [ - threeSpace(x + 80, bedLengthOuter) + bedXOffset, - threeSpace(y + 100, bedWidthOuter) + bedYOffset, - zZero - zDir * z + 245, - ] - : [ - threeSpace(x + 35, bedLengthOuter) + bedXOffset, - threeSpace(y, bedWidthOuter) + bedYOffset, - zZero - zDir * z + 245, - ], + airTubeEndPosition(config.kitVersion), ), 20, 5, 8]}> { /> + position={vacuumPumpCoverPosition(config.kitVersion)} /> { rotation={[Math.PI / 2, 0, 0]}> - - {config.kitVersion == "v1.7" - ? ( - range((botSizeY - 10) / 300).map((i) => ( - - - - )) - ) - : ( - { - const shape = new THREE.Shape(); - - shape.moveTo(0, 0); - shape.lineTo(0, 20); - shape.lineTo(-40, 20); - shape.lineTo(-41, 22.5); - shape.lineTo(-42.5, 22.5); - shape.lineTo(-41.5, 18.5); - shape.lineTo(-30, 18.5); - shape.lineTo(-25, 0); - shape.lineTo(0, 0); - return shape; - })(), - { - depth: botSizeY - 30, - bevelEnabled: false, - }, - )}> - - - )} - - - - + + ", () => { + const fakeProps = (): CableCarrierVerticalProps => ({ + config: clone(INITIAL), + }); + + it("renders v1.7", () => { + const p = fakeProps(); + p.config.kitVersion = "v1.7"; + const wrapper = render(); + expect(wrapper.container).toContainHTML("ccVertical"); + expect(wrapper.container.querySelectorAll("mesh").length).toBe(4); + }); + + it("renders v1.8", () => { + const p = fakeProps(); + p.config.kitVersion = "v1.8"; + const wrapper = render(); + expect(wrapper.container).toContainHTML("ccVertical"); + expect(wrapper.container.querySelectorAll("mesh").length).toBe(1); + }); +}); + + +describe("", () => { + const fakeProps = (): CableCarrierHorizontalProps => ({ + config: clone(INITIAL), + }); + + it("renders v1.7", () => { + const p = fakeProps(); + p.config.kitVersion = "v1.7"; + const wrapper = render(); + expect(wrapper.container).toContainHTML("ccHorizontal"); + expect(wrapper.container.querySelectorAll("mesh").length).toBe(5); + }); + + it("renders v1.8", () => { + const p = fakeProps(); + p.config.kitVersion = "v1.8"; + const wrapper = render(); + expect(wrapper.container).toContainHTML("ccHorizontal"); + expect(wrapper.container.querySelectorAll("mesh").length).toBe(1); + }); +}); diff --git a/frontend/three_d_garden/bot/components/cable_carriers.tsx b/frontend/three_d_garden/bot/components/cable_carriers.tsx new file mode 100644 index 0000000000..8bf17097ab --- /dev/null +++ b/frontend/three_d_garden/bot/components/cable_carriers.tsx @@ -0,0 +1,274 @@ +import React from "react"; +import * as THREE from "three"; +import { Extrude, useGLTF } from "@react-three/drei"; +import { Shape } from "three"; +import { + threeSpace, + zDir as zDirFunc, + zZero as zZeroFunc, +} from "../../helpers"; +import { Config } from "../../config"; +import { GLTF } from "three-stdlib"; +import { ASSETS, LIB_DIR, PartName } from "../../constants"; +import { range } from "lodash"; +import { Group, Mesh, MeshPhongMaterial } from "../../components"; +import { distinguishableBlack, extrusionWidth } from "../bot"; + +type CCHorizontal = GLTF & { + nodes: { [PartName.ccHorizontal]: THREE.Mesh }; + materials: never; +} +type CCVertical = GLTF & { + nodes: { [PartName.ccVertical]: THREE.Mesh }; + materials: never; +} + +const ccPath = + (axisLength: number, y: number, curveDia: number, isX?: boolean) => { + const lowerLength = (y + axisLength + 180) / 2; + const upperLength = lowerLength - y; + const outerRadius = curveDia / 2; + const height = isX ? 15 : 20; + const innerRadius = outerRadius - height; + + const path = new Shape(); + path.moveTo(y + 20, 0); + path.lineTo(y + upperLength, 0); + path.arc(0, outerRadius, outerRadius, -Math.PI / 2, Math.PI / 2); + path.lineTo(0, curveDia); + path.lineTo(0, curveDia - 5); + path.lineTo(20, curveDia - height); + path.lineTo(lowerLength, curveDia - height); + path.arc(0, -innerRadius, innerRadius, Math.PI / 2, -Math.PI / 2, true); + if (isX) { + path.lineTo(y + 20, height - 1); + path.lineTo(y, 5); + path.lineTo(y, 0); + } else { + path.lineTo(y, height - 1); + path.lineTo(y, height - 5); + } + path.lineTo(y + 20, 0); + return path; + }; + +interface CableCarrierXProps { + config: Config; +} + +export const CableCarrierX = (props: CableCarrierXProps) => { + const { + x, bedHeight, cableCarriers, botSizeX, bedLengthOuter, bedXOffset, + tracks, bedWidthOuter + } = props.config; + const bedCCSupportHeight = Math.min(150, bedHeight / 2); + return + + ; +}; + +interface CableCarrierYProps { + config: Config; +} + +export const CableCarrierY = (props: CableCarrierYProps) => { + const { + x, y, columnLength, cableCarriers, botSizeY, bedLengthOuter, bedYOffset, + bedXOffset, bedWidthOuter, kitVersion, + } = props.config; + const ccDepth = (kitVersion: string) => { + switch (kitVersion) { + case "v1.7": + return 60; + case "v1.8": + default: + return 40; + } + }; + return + + ; +}; + +interface CableCarrierZProps { + config: Config; +} + +export const CableCarrierZ = (props: CableCarrierZProps) => { + const { + x, y, z, cableCarriers, botSizeZ, zGantryOffset, bedLengthOuter, bedYOffset, + bedXOffset, bedWidthOuter, + } = props.config; + const zZero = zZeroFunc(props.config); + const zDir = zDirFunc(props.config); + return + + ; +}; + +export interface CableCarrierVerticalProps { + config: Config; +} + +export const CableCarrierVertical = (props: CableCarrierVerticalProps) => { + const { + x, y, z, bedLengthOuter, bedYOffset, bedXOffset, bedWidthOuter, zAxisLength, + kitVersion, + } = props.config; + const zZero = zZeroFunc(props.config); + const zDir = zDirFunc(props.config); + const ccVertical = useGLTF(ASSETS.models.ccVertical, LIB_DIR) as CCVertical; + switch (kitVersion) { + case "v1.7": + return + {range((zAxisLength - 350) / 200).map((i) => ( + + + + ))} + ; + case "v1.8": + return + { + const shape = new THREE.Shape(); + shape.moveTo(0, 0); + shape.lineTo(0, 20); + shape.lineTo(15, 20); + shape.lineTo(20, 1.5); + shape.lineTo(28.5, 1.5); + shape.lineTo(28.5, -61); + shape.lineTo(24, -63); + shape.lineTo(24, -61.5); + shape.lineTo(27, -60); + shape.lineTo(27, 0); + shape.lineTo(0, 0); + return shape; + })(), + { + depth: zAxisLength - 350, + bevelEnabled: false, + }, + )}> + + + ; + } +}; + +export interface CableCarrierHorizontalProps { + config: Config; +} + +export const CableCarrierHorizontal = (props: CableCarrierHorizontalProps) => { + const { + x, bedLengthOuter, bedYOffset, bedXOffset, bedWidthOuter, botSizeY, + columnLength, kitVersion, + } = props.config; + const ccHorizontal = useGLTF(ASSETS.models.ccHorizontal, LIB_DIR) as CCHorizontal; + switch (kitVersion) { + case "v1.7": + return + {range((botSizeY - 10) / 300).map((i) => ( + + + + ))}; + ; + case "v1.8": + return + { + const shape = new THREE.Shape(); + + shape.moveTo(0, 0); + shape.lineTo(0, 20); + shape.lineTo(-40, 20); + shape.lineTo(-41, 22.5); + shape.lineTo(-42.5, 22.5); + shape.lineTo(-41.5, 18.5); + shape.lineTo(-30, 18.5); + shape.lineTo(-25, 0); + shape.lineTo(0, 0); + return shape; + })(), + { + depth: botSizeY - 30, + bevelEnabled: false, + }, + )}> + + + ; + } +}; diff --git a/frontend/three_d_garden/bot/components/electronics_box.tsx b/frontend/three_d_garden/bot/components/electronics_box.tsx index 4bfeabc91b..0cde0c4db6 100644 --- a/frontend/three_d_garden/bot/components/electronics_box.tsx +++ b/frontend/three_d_garden/bot/components/electronics_box.tsx @@ -45,6 +45,35 @@ type Farmduino = GLTF & { materials: { PaletteMaterial001: THREE.MeshStandardMaterial }; } +const buttons = (kitVersion: string) => { + switch (kitVersion) { + case "v1.7": + return [ + { position: -60, color: IColor.estop.on }, + { position: -30, color: IColor.unlock.on }, + { position: 0, color: IColor.blank.on }, + { position: 30, color: IColor.blank.on }, + { position: 60, color: IColor.blank.on }, + ]; + case "v1.8": + default: + return [ + { position: -30, color: IColor.estop.on }, + { position: 0, color: IColor.unlock.on }, + { position: 30, color: IColor.blank.on }, + ]; + } +}; +const ledsPresent = (kitVersion: string) => { + switch (kitVersion) { + case "v1.7": + return true; + case "v1.8": + default: + return false; + } +}; + export interface ElectronicsBoxProps { config: Config; } @@ -82,21 +111,7 @@ export const ElectronicsBox = (props: ElectronicsBoxProps) => { scale={1000} /> - {( - props.config.kitVersion == "v1.7" - ? [ - { position: -60, color: IColor.estop.on }, - { position: -30, color: IColor.unlock.on }, - { position: 0, color: IColor.blank.on }, - { position: 30, color: IColor.blank.on }, - { position: 60, color: IColor.blank.on }, - ] - : [ - { position: -30, color: IColor.estop.on }, - { position: 0, color: IColor.unlock.on }, - { position: 30, color: IColor.blank.on }, - ] - ).map(button => { + {buttons(props.config.kitVersion).map(button => { const { position, color } = button; const btnPosition = position; return @@ -122,7 +137,7 @@ export const ElectronicsBox = (props: ElectronicsBoxProps) => { + visible={ledsPresent(props.config.kitVersion)}> {[ { position: -45, color: IColor.sync.on }, { position: -15, color: IColor.connect.on }, diff --git a/frontend/three_d_garden/bot/components/index.ts b/frontend/three_d_garden/bot/components/index.ts index 91b83999ae..f7ef365b06 100644 --- a/frontend/three_d_garden/bot/components/index.ts +++ b/frontend/three_d_garden/bot/components/index.ts @@ -4,3 +4,4 @@ export * from "./solenoid"; export * from "./tools"; export * from "./water_tube"; export * from "./x_axis_water_tube"; +export * from "./cable_carriers"; diff --git a/frontend/three_d_garden/config_overlays.tsx b/frontend/three_d_garden/config_overlays.tsx index 426f1c2b36..1117f9969c 100644 --- a/frontend/three_d_garden/config_overlays.tsx +++ b/frontend/three_d_garden/config_overlays.tsx @@ -279,6 +279,8 @@ export const PrivateOverlay = (props: OverlayProps) => { + => ({ z: zZero(config) + zDir(config) * config.botSizeZ, }); -interface Vector3Array extends Array { - 0: number; - 1: number; - 2: number; -} export const easyCubicBezierCurve3 = ( - startPosition: Vector3Array, - startControl: Vector3Array, - endControl: Vector3Array, - endPosition: Vector3Array, + startPosition: [number, number, number], + startControl: [number, number, number], + endControl: [number, number, number], + endPosition: [number, number, number], ) => { const [x1, y1, z1] = startPosition; const [x1c, y1c, z1c] = startControl; diff --git a/frontend/three_d_garden/index.tsx b/frontend/three_d_garden/index.tsx index bfda96c3ae..962f56fd85 100644 --- a/frontend/three_d_garden/index.tsx +++ b/frontend/three_d_garden/index.tsx @@ -7,7 +7,7 @@ import { AddPlantProps } from "./bed"; import { TaggedGenericPointer, TaggedWeedPointer } from "farmbot"; import { SlotWithTool } from "../resources/interfaces"; import { NavigateFunction } from "react-router"; -import { Path } from "../internal_urls"; +import { FilePath, Path } from "../internal_urls"; import { t } from "../i18next_wrapper"; import { Actions, Content, DeviceSetting } from "../constants"; import { isMobile } from "../screen_size"; @@ -30,7 +30,16 @@ export interface ThreeDGardenProps { export const ThreeDGarden = (props: ThreeDGardenProps) => { return
- + + {"Loading +

+ {t("Loading interactive 3D FarmBot...")} +

+
}> ", () => { ["farmduino_k15", 8], ["farmduino_k16", 9], ["farmduino_k17", 9], + ["farmduino_k18", 9], ["express_k10", 3], ["express_k11", 3], ["express_k12", 3], diff --git a/frontend/tools/add_tool.tsx b/frontend/tools/add_tool.tsx index 0e17b03282..a41e9dbc7b 100644 --- a/frontend/tools/add_tool.tsx +++ b/frontend/tools/add_tool.tsx @@ -110,6 +110,7 @@ export class RawAddTool extends React.Component { ]; case "farmduino_k16": case "farmduino_k17": + case "farmduino_k18": return [ ...BASE_TOOLS, t("Rotary Tool"), diff --git a/frontend/util/version.ts b/frontend/util/version.ts index 6233446737..e0976af308 100644 --- a/frontend/util/version.ts +++ b/frontend/util/version.ts @@ -85,6 +85,7 @@ export enum FbosVersionFallback { } const fallbackData: MinOsFeatureLookup = { + [Feature.farmduino_k18]: "15.4.11", [Feature.express_k12]: MinVersionOverride.NEVER, // available: "15.4.6", [Feature.planted_at_now]: MinVersionOverride.NEVER, }; diff --git a/frontend/wizard/__tests__/checks_test.tsx b/frontend/wizard/__tests__/checks_test.tsx index 04df6759cd..3912089451 100644 --- a/frontend/wizard/__tests__/checks_test.tsx +++ b/frontend/wizard/__tests__/checks_test.tsx @@ -104,7 +104,6 @@ const fakeProps = (): WizardStepComponentProps => ({ resources: buildResourceIndex([fakeDevice()]).index, bot: bot, dispatch: mockDispatch(), - navigate: jest.fn(), getConfigValue: jest.fn(), }); @@ -792,7 +791,7 @@ describe("", () => { expect(p.dispatch).toHaveBeenCalledWith({ type: Actions.SET_TOUR, payload: "gettingStarted", }); - expect(p.navigate).toHaveBeenCalledWith( + expect(mockNavigate).toHaveBeenCalledWith( tourPath("", "gettingStarted", "intro")); }); }); diff --git a/frontend/wizard/__tests__/index_test.tsx b/frontend/wizard/__tests__/index_test.tsx index e27c25bea1..d72f3808d7 100644 --- a/frontend/wizard/__tests__/index_test.tsx +++ b/frontend/wizard/__tests__/index_test.tsx @@ -6,7 +6,7 @@ jest.mock("../actions", () => ({ })); import React from "react"; -import { mount } from "enzyme"; +import { render, screen, fireEvent } from "@testing-library/react"; import { bot } from "../../__test_support__/fake_state/bot"; import { fakeTimeSettings } from "../../__test_support__/fake_time_settings"; import { @@ -15,7 +15,7 @@ import { import { mapStateToProps, RawSetupWizard as SetupWizard } from "../index"; import { SetupWizardProps } from "../interfaces"; import { fakeState } from "../../__test_support__/fake_state"; -import { WizardSectionSlug, WizardStepSlug, WIZARD_STEPS } from "../data"; +import { WizardStepSlug, WIZARD_STEPS } from "../data"; import { BooleanSetting } from "../../session_keys"; import { fakeUser, @@ -42,8 +42,8 @@ describe("", () => { it("renders", () => { const p = fakeProps(); p.device = undefined; - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("setup"); + render(); + expect(screen.getByText("Setup")).toBeInTheDocument(); }); it("renders with results", () => { @@ -52,8 +52,8 @@ describe("", () => { result.body.slug = WizardStepSlug.intro; result.body.answer = true; p.wizardStepResults = [result]; - const wrapper = mount(); - expect(wrapper.html()).toContain("fa-check"); + const { container } = render(); + expect(container).toContainHTML("fa-check"); }); it("renders with negative results", () => { @@ -62,15 +62,15 @@ describe("", () => { result.body.slug = WizardStepSlug.intro; result.body.answer = false; p.wizardStepResults = [result]; - const wrapper = mount(); - expect(wrapper.html()).toContain("fa-times"); + const { container } = render(); + expect(container).toContainHTML("fa-times"); }); it("renders when complete", () => { const p = fakeProps(); p.device && (p.device.body.setup_completed_at = "123"); - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("setup complete"); + render(); + expect(screen.getByText("Setup Complete!")).toBeInTheDocument(); }); it("resets setup", () => { @@ -78,46 +78,29 @@ describe("", () => { const result = fakeWizardStepResult(); result.body.slug = "slug"; p.wizardStepResults = [result]; - const wrapper = mount(); - expect(wrapper.instance().results).toEqual({ slug: result.body }); - wrapper.instance().reset(); + render(); + const reset = screen.getByText("start over"); + fireEvent.click(reset); expect(destroyAllWizardStepResults).toHaveBeenCalledTimes(1); }); - it("toggles section", () => { - const wrapper = mount(); - expect(wrapper.state().intro).toEqual(true); - wrapper.instance().toggleSection(WizardSectionSlug.intro)(); - expect(wrapper.state().intro).toEqual(false); - }); - - it("opens step", () => { - const wrapper = mount(); - wrapper.setState({ stepOpen: undefined }); - wrapper.instance().openStep(WizardStepSlug.intro)(); - expect(wrapper.state().stepOpen).toEqual(WizardStepSlug.intro); - }); - - it("closes step", () => { - const wrapper = mount(); - expect(wrapper.state().stepOpen).toEqual(WizardStepSlug.intro); - wrapper.instance().openStep(WizardStepSlug.intro)(); - expect(wrapper.state().stepOpen).toEqual(undefined); + it("opens and closes step", () => { + const text = "What is your preferred language?"; + render(); + expect(screen.queryByText(text)).not.toBeInTheDocument(); + const step = screen.getByText("Language"); + fireEvent.click(step); + expect(screen.getByText(text)).toBeInTheDocument(); + fireEvent.click(step); }); it("updates data", async () => { - const wrapper = mount(); - expect(wrapper.instance().results).toEqual({}); - await wrapper.instance().updateData(fakeWizardStepResult().body)(); - expect(addOrUpdateWizardStepResult).toHaveBeenCalled(); - }); - - it("updates data and progresses to next step", async () => { - const wrapper = mount(); - expect(wrapper.state().stepOpen).toEqual(WizardStepSlug.intro); - await wrapper.instance().updateData( - fakeWizardStepResult().body, WizardStepSlug.orderInfo)(); - expect(wrapper.state().stepOpen).toEqual(WizardStepSlug.orderInfo); + render(); + expect(screen.getByText("Begin?")).toBeInTheDocument(); + const yes = screen.getByText("yes"); + await fireEvent.click(yes); + expect(addOrUpdateWizardStepResult).toHaveBeenCalledWith([], + { answer: true, outcome: undefined, slug: "intro" }); }); it("updates data and completes setup", async () => { @@ -130,13 +113,14 @@ describe("", () => { stepResult.body.slug = step.slug; return stepResult; }); - const wrapper = mount(); - const result = fakeWizardStepResult().body; - result.slug = WizardStepSlug.intro; - result.answer = true; - result.outcome = undefined; - await wrapper.instance().updateData(result, undefined, true)(); - await expect(completeSetup).toHaveBeenCalled(); + render(); + const header = screen.getByText("TOURS"); + fireEvent.click(header); + const step = screen.getByText("Setting up slots"); + fireEvent.click(step); + const yes = screen.getByText("yes"); + await fireEvent.click(yes); + expect(completeSetup).toHaveBeenCalled(); }); }); diff --git a/frontend/wizard/__tests__/prerequisites_test.tsx b/frontend/wizard/__tests__/prerequisites_test.tsx index e49f23aae7..f89bd083b5 100644 --- a/frontend/wizard/__tests__/prerequisites_test.tsx +++ b/frontend/wizard/__tests__/prerequisites_test.tsx @@ -22,7 +22,6 @@ describe("", () => { resources: buildResourceIndex([fakeDevice()]).index, bot: bot, dispatch: mockDispatch(), - navigate: jest.fn(), getConfigValue: jest.fn(), }); diff --git a/frontend/wizard/__tests__/step_test.tsx b/frontend/wizard/__tests__/step_test.tsx index 4f7417c2b3..95fb1ae801 100644 --- a/frontend/wizard/__tests__/step_test.tsx +++ b/frontend/wizard/__tests__/step_test.tsx @@ -45,7 +45,6 @@ describe("", () => { resources: buildResourceIndex([]).index, bot: bot, dispatch: jest.fn(), - navigate: jest.fn(), getConfigValue: jest.fn(), }); diff --git a/frontend/wizard/checks.tsx b/frontend/wizard/checks.tsx index 924143ebdc..b32a1c01e1 100644 --- a/frontend/wizard/checks.tsx +++ b/frontend/wizard/checks.tsx @@ -341,10 +341,12 @@ const SEED_DATA_OPTION_TO_FW_HARDWARE: Record = { "genesis_1.5": "farmduino_k15", "genesis_1.6": "farmduino_k16", "genesis_1.7": "farmduino_k17", + "genesis_1.8": "farmduino_k18", "genesis_xl_1.4": "farmduino_k14", "genesis_xl_1.5": "farmduino_k15", "genesis_xl_1.6": "farmduino_k16", "genesis_xl_1.7": "farmduino_k17", + "genesis_xl_1.8": "farmduino_k18", "express_1.0": "express_k10", "express_1.1": "express_k11", "express_1.2": "express_k12", @@ -361,6 +363,7 @@ const FW_HARDWARE_TO_RPI: Record = { "farmduino_k15": "3", "farmduino_k16": undefined, "farmduino_k17": "4", + "farmduino_k18": "4", "express_k10": "01", "express_k11": "02", "express_k12": "02", @@ -803,6 +806,7 @@ export const CameraReplacement = () =>
; export const Tour = (tourSlug: string) => { + const navigate = useNavigate(); return (props: WizardStepComponentProps) => ; diff --git a/frontend/wizard/index.tsx b/frontend/wizard/index.tsx index dcf9f90289..b45e21febf 100644 --- a/frontend/wizard/index.tsx +++ b/frontend/wizard/index.tsx @@ -14,7 +14,7 @@ import { WIZARD_STEPS, WizardSectionSlug, WizardStepSlug, setupProgressString, } from "./data"; import { - SetupWizardProps, SetupWizardState, WizardHeaderProps, WizardResults, + SetupWizardProps, WizardHeaderProps, WizardResults, WizardSectionHeaderProps, WizardSectionsOpen, WizardStepDataProps, } from "./interfaces"; import { @@ -32,7 +32,6 @@ import { completeSetup, resetSetup, } from "./actions"; -import { NavigationContext } from "../routes_helpers"; export const mapStateToProps = (props: Everything): SetupWizardProps => ({ resources: props.resources.index, @@ -45,31 +44,33 @@ export const mapStateToProps = (props: Everything): SetupWizardProps => ({ device: maybeGetDevice(props.resources.index), }); -export class RawSetupWizard - extends React.Component { - - get stepDataProps(): WizardStepDataProps { - return { - firmwareHardware: this.props.firmwareHardware, - getConfigValue: this.props.getConfigValue, - }; - } - - get results() { - const results: WizardResults = {}; - this.props.wizardStepResults.map(result => { - results[result.body.slug as WizardStepSlug] = result.body; - }); - return results; - } - - sectionsOpen = () => { +export const RawSetupWizard = (props: SetupWizardProps) => { + const stepDataProps: WizardStepDataProps = { + firmwareHardware: props.firmwareHardware, + getConfigValue: props.getConfigValue, + }; + const wizardSections = WIZARD_SECTIONS(stepDataProps); + const wizardSteps = WIZARD_STEPS(stepDataProps); + const wizardStepSlugs = WIZARD_STEP_SLUGS(stepDataProps); + + const results: WizardResults = + props.wizardStepResults + .reduce((acc, result) => { + acc[result.body.slug as WizardStepSlug] = result.body; + return acc; + }, {} as WizardResults); + + const [currentlyOpenStep, setCurrentlyOpenStep] = + React.useState(wizardStepSlugs + .filter(slug => !results[slug]?.answer)[0]); + + const sectionsOpenInit = () => { const open: Partial = {}; let oneOpen = false; - WIZARD_SECTIONS(this.stepDataProps).map(section => { + wizardSections.map(section => { if (!oneOpen) { const sectionOpen = some(section.steps.map(step => - !this.results[step.slug]?.answer)); + !results[step.slug]?.answer)); open[section.slug] = sectionOpen; oneOpen = sectionOpen || oneOpen; } @@ -77,129 +78,131 @@ export class RawSetupWizard return open as WizardSectionsOpen; }; - state: SetupWizardState = { - ...this.sectionsOpen(), - stepOpen: WIZARD_STEP_SLUGS(this.stepDataProps) - .filter(slug => !this.results[slug]?.answer)[0], - }; + const allSectionsClosed: WizardSectionsOpen = + wizardSections + .reduce((acc, section) => { + acc[section.slug] = false; + return acc; + }, {} as WizardSectionsOpen); - reset = () => { - this.props.dispatch(destroyAllWizardStepResults( - this.props.wizardStepResults)) + const [sectionsOpen, setSectionsOpen] = + React.useState(sectionsOpenInit()); + + const reset = () => { + props.dispatch(destroyAllWizardStepResults( + props.wizardStepResults)) .then(() => { - this.setState({ - stepOpen: WIZARD_STEP_SLUGS(this.stepDataProps)[0], - ...this.closedSections, - ...this.sectionsOpen(), + setCurrentlyOpenStep(wizardStepSlugs[0]); + setSectionsOpen({ + ...allSectionsClosed, + [stepSection(wizardStepSlugs[0])]: true, }); - this.props.dispatch(resetSetup(this.props.device)); + props.dispatch(resetSetup(props.device)); }) .catch(noop); }; - get closedSections() { - const sectionStates: Partial> = {}; - WIZARD_SECTIONS(this.stepDataProps) - .map(section => { sectionStates[section.slug] = false; }); - return sectionStates; - } - - updateData = ( + const updateData = ( stepResult: WizardStepResult, nextStepSlug?: WizardStepSlug, last?: boolean, ) => () => { - this.props.dispatch(addOrUpdateWizardStepResult( - this.props.wizardStepResults, stepResult)) + props.dispatch(addOrUpdateWizardStepResult( + props.wizardStepResults, stepResult)) .then(() => { - this.setState({ - stepOpen: last ? undefined : (nextStepSlug || this.state.stepOpen), - ...((last || nextStepSlug) ? this.closedSections : {}), - ...(nextStepSlug ? { [this.stepSection(nextStepSlug)]: true } : {}), - }); - this.props.wizardStepResults.filter(result => result.body.answer).length - == WIZARD_STEPS(this.stepDataProps).length - && this.props.dispatch(completeSetup(this.props.device)); + if (last) { + setCurrentlyOpenStep(undefined); + setSectionsOpen(allSectionsClosed); + } + if (nextStepSlug) { + setCurrentlyOpenStep(nextStepSlug); + setSectionsOpen({ + ...allSectionsClosed, + [stepSection(nextStepSlug)]: true, + }); + } + props.wizardStepResults.filter(result => + result.body.answer).length == wizardSteps.length + && props.dispatch(completeSetup(props.device)); }); }; - getNextStepSlug = (stepSlug: WizardStepSlug) => { - const slugs = WIZARD_STEP_SLUGS(this.stepDataProps) - .filter(slug => this.props.device?.body.setup_completed_at - || !this.results[slug]?.answer); + const getNextStepSlug = (stepSlug: WizardStepSlug) => { + const slugs = wizardStepSlugs + .filter(slug => props.device?.body.setup_completed_at + || !results[slug]?.answer); return slugs[slugs.indexOf(stepSlug) + 1]; }; - setStepSuccess = (stepSlug: WizardStepSlug) => + const setStepSuccess = (stepSlug: WizardStepSlug) => (success: boolean, outcome?: string) => { - const nextSlug = success ? this.getNextStepSlug(stepSlug) : undefined; - return this.updateData({ + const nextSlug = success ? getNextStepSlug(stepSlug) : undefined; + return updateData({ slug: stepSlug, outcome: success ? undefined - : (outcome || this.results[stepSlug]?.outcome), + : (outcome || results[stepSlug]?.outcome), answer: success, }, nextSlug, success && isUndefined(nextSlug)); }; - toggleSection = (slug: WizardSectionSlug) => () => - this.setState({ ...this.state, [slug]: !this.state[slug] }); - - stepSection = (stepSlug: WizardStepSlug): WizardSectionSlug => - WIZARD_STEPS(this.stepDataProps) - .filter(step => step.slug == stepSlug)[0].section; - - openStep = (stepSlug: WizardStepSlug) => () => this.setState({ - stepOpen: this.state.stepOpen == stepSlug ? undefined : stepSlug, - [this.stepSection(stepSlug)]: true, - }); - - static contextType = NavigationContext; - context!: React.ContextType; - navigate = this.context; - - render() { - const panelName = "setup"; - return - - - - {WIZARD_SECTIONS(this.stepDataProps) - .map(section => -
- - - {section.steps.map(step => - )} - -
)} - {this.props.device?.body.setup_completed_at && -
- -

{t("Setup Complete!")}

-
} -
-
; - } -} + const stepSection = (stepSlug: WizardStepSlug): WizardSectionSlug => + wizardSteps.filter(step => step.slug == stepSlug)[0].section; + + const openStep = (stepSlug: WizardStepSlug) => () => { + setCurrentlyOpenStep(currentlyOpenStep == stepSlug ? undefined : stepSlug); + setSectionsOpen({ + ...sectionsOpen, + [stepSection(stepSlug)]: true, + }); + }; + + const toggleSection = (sectionSlug: WizardSectionSlug) => () => { + setSectionsOpen({ + ...sectionsOpen, + [sectionSlug]: !sectionsOpen[sectionSlug], + }); + }; + + const panelName = "setup"; + return + + + + {wizardSections + .map(section => +
+ + + {section.steps.map(step => + )} + +
)} + {props.device?.body.setup_completed_at && +
+ +

{t("Setup Complete!")}

+
} +
+
; +}; const WizardHeader = (props: WizardHeaderProps) =>
diff --git a/frontend/wizard/interfaces.ts b/frontend/wizard/interfaces.ts index 666d28db85..504cff5f73 100644 --- a/frontend/wizard/interfaces.ts +++ b/frontend/wizard/interfaces.ts @@ -9,7 +9,6 @@ import { TimeSettings } from "../interfaces"; import { ResourceIndex } from "../resources/interfaces"; import { ControlsCheckOptions, PinBindingOptions } from "./checks"; import { WizardSectionSlug, WizardStepSlug } from "./data"; -import { NavigateFunction } from "react-router"; export interface SetupWizardProps extends WizardOutcomeComponentProps { timeSettings: TimeSettings; @@ -56,7 +55,6 @@ export interface WizardOutcomeComponentProps { export interface WizardStepComponentProps extends WizardOutcomeComponentProps { setStepSuccess(success: boolean, outcome?: string): () => void; - navigate: NavigateFunction; } interface ComponentOptions { @@ -98,11 +96,8 @@ export type WizardSteps = WizardStep[]; export type WizardResults = Partial>; -export type WizardSectionsOpen = Partial>; +export type WizardSectionsOpen = Record; -export interface SetupWizardState extends WizardSectionsOpen { - stepOpen: WizardStepSlug | undefined; -} export interface WizardStepDataProps { firmwareHardware: FirmwareHardware | undefined; @@ -141,7 +136,6 @@ export interface WizardStepContainerProps extends WizardOutcomeComponentProps { setStepSuccess(stepSlug: WizardStepSlug): (success: boolean, outcome?: string) => () => void; timeSettings: TimeSettings; - navigate: NavigateFunction; } export interface TroubleshootingTipsProps extends WizardOutcomeComponentProps { diff --git a/frontend/wizard/step.tsx b/frontend/wizard/step.tsx index 5b7529cdd1..26b635dd59 100644 --- a/frontend/wizard/step.tsx +++ b/frontend/wizard/step.tsx @@ -88,7 +88,6 @@ export const WizardStepContainer = (props: WizardStepContainerProps) => { } {step.controlsCheckOptions && diff --git a/package.json b/package.json index 190b70213b..4f4e084e1b 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,8 @@ "@parcel/watcher": "2.1.0" }, "dependencies": { - "@blueprintjs/core": "5.18.0", - "@blueprintjs/select": "5.3.19", + "@blueprintjs/core": "5.19.0", + "@blueprintjs/select": "5.3.20", "@monaco-editor/react": "4.7.0", "@parcel/transformer-sass": "2.14.4", "@parcel/transformer-typescript-tsc": "2.14.4", @@ -48,26 +48,26 @@ "@rollbar/react": "0.12.1", "@types/lodash": "4.17.16", "@types/markdown-it": "14.1.2", - "@types/node": "22.14.0", + "@types/node": "22.15.14", "@types/promise-timeout": "1.3.3", - "@types/react": "19.1.1", + "@types/react": "19.1.3", "@types/react-color": "3.0.13", - "@types/react-dom": "19.1.2", - "@types/three": "0.175.0", + "@types/react-dom": "19.1.3", + "@types/three": "0.176.0", "@types/ws": "8.18.1", "@xterm/xterm": "5.5.0", - "axios": "1.8.4", + "axios": "1.9.0", "bowser": "2.11.0", "browser-speech": "1.1.1", "events": "3.3.0", - "farmbot": "15.8.8", - "i18next": "24.2.3", + "farmbot": "15.8.11", + "i18next": "25.1.1", "lodash": "4.17.21", "markdown-it": "14.1.0", "markdown-it-emoji": "3.0.0", "moment": "2.30.1", "monaco-editor": "0.52.2", - "mqtt": "5.11.0", + "mqtt": "5.12.1", "npm": "11.3.0", "parcel": "2.14.4", "process": "0.11.10", @@ -78,7 +78,7 @@ "react-color": "2.19.3", "react-dom": "18.3.1", "react-redux": "9.2.0", - "react-router": "7.5.0", + "react-router": "7.5.3", "redux": "5.0.1", "redux-immutable-state-invariant": "2.1.0", "redux-thunk": "3.1.0", @@ -121,9 +121,9 @@ "raf": "3.4.1", "react-addons-test-utils": "15.6.2", "react-test-renderer": "18.3.1", - "sass": "1.86.3", + "sass": "1.87.0", "sass-lint": "1.13.1", - "ts-jest": "29.3.1", + "ts-jest": "29.3.2", "tslint": "5.20.1" } } diff --git a/spec/controllers/api/devices/devices_controller_seed_spec.rb b/spec/controllers/api/devices/devices_controller_seed_spec.rb index 74f8ee2125..dbad44437f 100644 --- a/spec/controllers/api/devices/devices_controller_seed_spec.rb +++ b/spec/controllers/api/devices/devices_controller_seed_spec.rb @@ -251,6 +251,14 @@ def settings_default_map_size_y?(device) device.web_app_config.map_size_y end + def settings_three_d_garden?(device) + device.web_app_config.three_d_garden + end + + def settings_3d?(device) + device.farmware_envs.find_by(key: "3D_beamLength")&.value + end + it "seeds accounts with default data" do sign_in user device = user.device @@ -329,6 +337,8 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(120) expect(settings_firmware?(device)).to eq("arduino") expect(settings_hide_sensors?(device)).to be(false) + expect(settings_three_d_garden?(device)).to be(false) + expect(settings_3d?(device)).to_not be expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device).name).to eq("Slot") @@ -402,6 +412,7 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(120) expect(settings_firmware?(device)).to eq("farmduino") expect(settings_hide_sensors?(device)).to be(false) + expect(settings_3d?(device)).to_not be expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device).name).to eq("Slot") @@ -475,6 +486,7 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(120) expect(settings_firmware?(device)).to eq("farmduino_k14") expect(settings_hide_sensors?(device)).to be(false) + expect(settings_3d?(device)).to_not be expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device).name).to eq("Slot") @@ -548,6 +560,7 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(120) expect(settings_firmware?(device)).to eq("farmduino_k15") expect(settings_hide_sensors?(device)).to be(false) + expect(settings_3d?(device)).to_not be expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device).name).to eq("Slot") @@ -623,6 +636,7 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(120) expect(settings_firmware?(device)).to eq("farmduino_k16") expect(settings_hide_sensors?(device)).to be(false) + expect(settings_3d?(device)).to_not be expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device).name).to eq("Slot") @@ -699,6 +713,83 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(120) expect(settings_firmware?(device)).to eq("farmduino_k17") expect(settings_hide_sensors?(device)).to be(false) + expect(settings_3d?(device)).to_not be + expect(tool_slots_slot_1?(device).name).to eq("Slot") + expect(tool_slots_slot_2?(device).name).to eq("Slot") + expect(tool_slots_slot_3?(device).name).to eq("Slot") + expect(tool_slots_slot_4?(device).name).to eq("Slot") + expect(tool_slots_slot_5?(device).name).to eq("Slot") + expect(tool_slots_slot_6?(device).name).to eq("Slot") + expect(tool_slots_slot_7?(device).name).to eq("Slot") + expect(tool_slots_slot_8?(device).name).to eq("Slot") + expect(tool_slots_slot_9?(device)).to_not be + + check_slot_pairing(tool_slots_slot_1?(device), "Seeder") + check_slot_pairing(tool_slots_slot_2?(device), "Seed Bin") + check_slot_pairing(tool_slots_slot_3?(device), "Seed Tray") + check_slot_pairing(tool_slots_slot_4?(device), "Watering Nozzle") + check_slot_pairing(tool_slots_slot_5?(device), "Soil Sensor") + check_slot_pairing(tool_slots_slot_6?(device), "Rotary Tool") + check_slot_pairing(tool_slots_slot_7?(device), "Seed Trough 1") + check_slot_pairing(tool_slots_slot_8?(device), "Seed Trough 2") + + expect(tools_seed_bin?(device)).to be + expect(tools_seed_tray?(device)).to be + expect(tools_seed_trough_1?(device)).to be + expect(tools_seed_trough_2?(device)).to be + expect(tools_seeder?(device)).to be_kind_of(Tool) + expect(tools_soil_sensor?(device)).to be_kind_of(Tool) + expect(tools_watering_nozzle?(device)).to be_kind_of(Tool) + expect(tools_weeder?(device)).to_not be + expect(tools_rotary?(device)).to be_kind_of(Tool) + expect(sequences_pickup_seed?(device)).to be + expect(sequences_plant_seed?(device)).to be_kind_of(Sequence) + expect(sequences_take_photo_of_plant?(device)).to be_kind_of(Sequence) + expect(sequences_water_plant?(device)).to be_kind_of(Sequence) + expect(point_groups_spinach?(device)).to_not be + expect(point_groups_broccoli?(device)).to_not be + expect(point_groups_beet?(device)).to_not be + expect(point_groups_all_plants?(device)).to be_kind_of(PointGroup) + expect(point_groups_all_points?(device)).to be_kind_of(PointGroup) + expect(point_groups_all_weeds?(device)).to be_kind_of(PointGroup) + expect(sequences_find_home?(device)).to be_kind_of(Sequence) + expect(sequences_water_all_plants?(device)).to be_kind_of(Sequence) + expect(sequences_water_all?(device)).to be_kind_of(Sequence) + expect(sequences_photo_grid?(device)).to be_kind_of(Sequence) + expect(sequences_weed_detection_grid?(device)).to be_kind_of(Sequence) + expect(sequences_soil_height_grid?(device)).to be_kind_of(Sequence) + expect(sequences_grid?(device)).to be_kind_of(Sequence) + expect(sequences_dispense_water?(device)).to be_kind_of(Sequence) + expect(sequences_mount_tool?(device)).to be_kind_of(Sequence) + expect(sequences_dismount_tool?(device)).to be_kind_of(Sequence) + expect(sequences_mow_all_weeds?(device)).to be_kind_of(Sequence) + expect(sequences_pick_from_seed_tray?(device)).to be_kind_of(Sequence) + expect(settings_default_map_size_x?(device)).to eq(2900) + expect(settings_default_map_size_y?(device)).to eq(1230) + end + + it "seeds accounts with Genesis 1.8 data" do + start_tests "genesis_1.8" + + expect(peripherals_lighting?(device).pin).to eq(7) + expect(peripherals_peripheral_4?(device).pin).to eq(10) + expect(peripherals_peripheral_5?(device).pin).to eq(12) + expect(peripherals_vacuum?(device).pin).to be(9) + expect(peripherals_water?(device).pin).to be(8) + expect(peripherals_rotary_tool?(device).pin).to eq(2) + expect(peripherals_rotary_tool_reverse?(device).pin).to eq(3) + expect(pin_bindings_button_1?(device).special_action).to eq("emergency_lock") + expect(pin_bindings_button_2?(device).special_action).to eq("emergency_unlock") + expect(plant_count?(device)).to eq(0) + expect(sensors_soil_sensor?(device).pin).to eq(59) + expect(sensors_tool_verification?(device).pin).to eq(63) + expect(settings_device_name?(device)).to eq(Names::GENESIS) + expect(settings_change_firmware_config_defaults?(device)).to be(true) + expect(settings_soil_height?(device)).to eq(-500) + expect(settings_gantry_height?(device)).to eq(120) + expect(settings_firmware?(device)).to eq("farmduino_k18") + expect(settings_hide_sensors?(device)).to be(false) + expect(settings_3d?(device)).to_not be expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device).name).to eq("Slot") @@ -774,6 +865,7 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(120) expect(settings_firmware?(device)).to eq("farmduino_k14") expect(settings_hide_sensors?(device)).to be(false) + expect(settings_3d?(device)).to eq("3000") expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device).name).to eq("Slot") @@ -847,6 +939,7 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(120) expect(settings_firmware?(device)).to eq("farmduino_k15") expect(settings_hide_sensors?(device)).to be(false) + expect(settings_3d?(device)).to eq("3000") expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device).name).to eq("Slot") @@ -922,6 +1015,83 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(120) expect(settings_firmware?(device)).to eq("farmduino_k17") expect(settings_hide_sensors?(device)).to be(false) + expect(settings_3d?(device)).to eq("3000") + expect(tool_slots_slot_1?(device).name).to eq("Slot") + expect(tool_slots_slot_2?(device).name).to eq("Slot") + expect(tool_slots_slot_3?(device).name).to eq("Slot") + expect(tool_slots_slot_4?(device).name).to eq("Slot") + expect(tool_slots_slot_5?(device).name).to eq("Slot") + expect(tool_slots_slot_6?(device).name).to eq("Slot") + expect(tool_slots_slot_7?(device).name).to eq("Slot") + expect(tool_slots_slot_8?(device).name).to eq("Slot") + expect(tool_slots_slot_9?(device)).to_not be + + check_slot_pairing(tool_slots_slot_1?(device), "Seeder") + check_slot_pairing(tool_slots_slot_2?(device), "Seed Bin") + check_slot_pairing(tool_slots_slot_3?(device), "Seed Tray") + check_slot_pairing(tool_slots_slot_4?(device), "Watering Nozzle") + check_slot_pairing(tool_slots_slot_5?(device), "Soil Sensor") + check_slot_pairing(tool_slots_slot_6?(device), "Rotary Tool") + check_slot_pairing(tool_slots_slot_7?(device), "Seed Trough 1") + check_slot_pairing(tool_slots_slot_8?(device), "Seed Trough 2") + + expect(tools_seed_bin?(device)).to be + expect(tools_seed_tray?(device)).to be + expect(tools_seed_trough_1?(device)).to be + expect(tools_seed_trough_2?(device)).to be + expect(tools_seeder?(device)).to be_kind_of(Tool) + expect(tools_soil_sensor?(device)).to be_kind_of(Tool) + expect(tools_watering_nozzle?(device)).to be_kind_of(Tool) + expect(tools_weeder?(device)).to_not be + expect(tools_rotary?(device)).to be_kind_of(Tool) + expect(sequences_pickup_seed?(device)).to be + expect(sequences_plant_seed?(device)).to be_kind_of(Sequence) + expect(sequences_take_photo_of_plant?(device)).to be_kind_of(Sequence) + expect(sequences_water_plant?(device)).to be_kind_of(Sequence) + expect(point_groups_spinach?(device)).to_not be + expect(point_groups_broccoli?(device)).to_not be + expect(point_groups_beet?(device)).to_not be + expect(point_groups_all_plants?(device)).to be_kind_of(PointGroup) + expect(point_groups_all_points?(device)).to be_kind_of(PointGroup) + expect(point_groups_all_weeds?(device)).to be_kind_of(PointGroup) + expect(sequences_find_home?(device)).to be_kind_of(Sequence) + expect(sequences_water_all_plants?(device)).to be_kind_of(Sequence) + expect(sequences_water_all?(device)).to be_kind_of(Sequence) + expect(sequences_photo_grid?(device)).to be_kind_of(Sequence) + expect(sequences_weed_detection_grid?(device)).to be_kind_of(Sequence) + expect(sequences_soil_height_grid?(device)).to be_kind_of(Sequence) + expect(sequences_grid?(device)).to be_kind_of(Sequence) + expect(sequences_dispense_water?(device)).to be_kind_of(Sequence) + expect(sequences_mount_tool?(device)).to be_kind_of(Sequence) + expect(sequences_dismount_tool?(device)).to be_kind_of(Sequence) + expect(sequences_mow_all_weeds?(device)).to be_kind_of(Sequence) + expect(sequences_pick_from_seed_tray?(device)).to be_kind_of(Sequence) + expect(settings_default_map_size_x?(device)).to eq(5900) + expect(settings_default_map_size_y?(device)).to eq(2730) + end + + it "seeds accounts with Genesis XL 1.8 data" do + start_tests "genesis_xl_1.8" + + expect(peripherals_lighting?(device).pin).to eq(7) + expect(peripherals_peripheral_4?(device).pin).to eq(10) + expect(peripherals_peripheral_5?(device).pin).to eq(12) + expect(peripherals_vacuum?(device).pin).to be(9) + expect(peripherals_water?(device).pin).to be(8) + expect(peripherals_rotary_tool?(device).pin).to eq(2) + expect(peripherals_rotary_tool_reverse?(device).pin).to eq(3) + expect(pin_bindings_button_1?(device).special_action).to eq("emergency_lock") + expect(pin_bindings_button_2?(device).special_action).to eq("emergency_unlock") + expect(plant_count?(device)).to eq(0) + expect(sensors_soil_sensor?(device).pin).to eq(59) + expect(sensors_tool_verification?(device).pin).to eq(63) + expect(settings_device_name?(device)).to eq(Names::GENESIS_XL) + expect(settings_change_firmware_config_defaults?(device)).to be(true) + expect(settings_soil_height?(device)).to eq(-500) + expect(settings_gantry_height?(device)).to eq(120) + expect(settings_firmware?(device)).to eq("farmduino_k18") + expect(settings_hide_sensors?(device)).to be(false) + expect(settings_3d?(device)).to eq("3000") expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device).name).to eq("Slot") @@ -997,6 +1167,7 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(120) expect(settings_firmware?(device)).to eq("farmduino_k16") expect(settings_hide_sensors?(device)).to be(false) + expect(settings_3d?(device)).to eq("3000") expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device).name).to eq("Slot") @@ -1073,6 +1244,7 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(140) expect(settings_firmware?(device)).to eq("express_k10") expect(settings_hide_sensors?(device)).to be(true) + expect(settings_3d?(device)).to eq("1200") expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device)).to_not be @@ -1118,7 +1290,7 @@ def check_slot_pairing(slot, expected_name) expect(sequences_mow_all_weeds?(device)).to_not be expect(sequences_pick_from_seed_tray?(device)).to_not be expect(settings_default_map_size_x?(device)).to eq(2900) - expect(settings_default_map_size_y?(device)).to eq(1200) + expect(settings_default_map_size_y?(device)).to eq(930) end it "seeds accounts with Express 1.1 data" do @@ -1142,6 +1314,7 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(140) expect(settings_firmware?(device)).to eq("express_k11") expect(settings_hide_sensors?(device)).to be(true) + expect(settings_3d?(device)).to eq("1200") expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device)).to_not be @@ -1187,7 +1360,7 @@ def check_slot_pairing(slot, expected_name) expect(sequences_mow_all_weeds?(device)).to_not be expect(sequences_pick_from_seed_tray?(device)).to_not be expect(settings_default_map_size_x?(device)).to eq(2900) - expect(settings_default_map_size_y?(device)).to eq(1200) + expect(settings_default_map_size_y?(device)).to eq(930) end it "seeds accounts with Express 1.2 data" do @@ -1211,6 +1384,7 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(140) expect(settings_firmware?(device)).to eq("express_k12") expect(settings_hide_sensors?(device)).to be(true) + expect(settings_3d?(device)).to eq("1200") expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device)).to_not be @@ -1256,7 +1430,7 @@ def check_slot_pairing(slot, expected_name) expect(sequences_mow_all_weeds?(device)).to_not be expect(sequences_pick_from_seed_tray?(device)).to_not be expect(settings_default_map_size_x?(device)).to eq(2900) - expect(settings_default_map_size_y?(device)).to eq(1200) + expect(settings_default_map_size_y?(device)).to eq(930) end it "seeds accounts with Express XL 1.0 data" do @@ -1280,6 +1454,7 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(140) expect(settings_firmware?(device)).to eq("express_k10") expect(settings_hide_sensors?(device)).to be(true) + expect(settings_3d?(device)).to eq("2400") expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device)).to_not be @@ -1324,8 +1499,8 @@ def check_slot_pairing(slot, expected_name) expect(sequences_dismount_tool?(device)).to_not be expect(sequences_mow_all_weeds?(device)).to_not be expect(sequences_pick_from_seed_tray?(device)).to_not be - expect(settings_default_map_size_x?(device)).to eq(6000) - expect(settings_default_map_size_y?(device)).to eq(2400) + expect(settings_default_map_size_x?(device)).to eq(5900) + expect(settings_default_map_size_y?(device)).to eq(2130) end it "seeds accounts with Express XL 1.1 data" do @@ -1349,6 +1524,7 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(140) expect(settings_firmware?(device)).to eq("express_k11") expect(settings_hide_sensors?(device)).to be(true) + expect(settings_3d?(device)).to eq("2400") expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device)).to_not be @@ -1393,8 +1569,8 @@ def check_slot_pairing(slot, expected_name) expect(sequences_dismount_tool?(device)).to_not be expect(sequences_mow_all_weeds?(device)).to_not be expect(sequences_pick_from_seed_tray?(device)).to_not be - expect(settings_default_map_size_x?(device)).to eq(6000) - expect(settings_default_map_size_y?(device)).to eq(2400) + expect(settings_default_map_size_x?(device)).to eq(5900) + expect(settings_default_map_size_y?(device)).to eq(2130) end it "seeds accounts with Express XL 1.2 data" do @@ -1418,6 +1594,7 @@ def check_slot_pairing(slot, expected_name) expect(settings_gantry_height?(device)).to eq(140) expect(settings_firmware?(device)).to eq("express_k12") expect(settings_hide_sensors?(device)).to be(true) + expect(settings_3d?(device)).to eq("2400") expect(tool_slots_slot_1?(device).name).to eq("Slot") expect(tool_slots_slot_2?(device).name).to eq("Slot") expect(tool_slots_slot_3?(device)).to_not be @@ -1462,8 +1639,8 @@ def check_slot_pairing(slot, expected_name) expect(sequences_dismount_tool?(device)).to_not be expect(sequences_mow_all_weeds?(device)).to_not be expect(sequences_pick_from_seed_tray?(device)).to_not be - expect(settings_default_map_size_x?(device)).to eq(6000) - expect(settings_default_map_size_y?(device)).to eq(2400) + expect(settings_default_map_size_x?(device)).to eq(5900) + expect(settings_default_map_size_y?(device)).to eq(2130) end it "seeds accounts with demo account data" do @@ -1473,9 +1650,10 @@ def check_slot_pairing(slot, expected_name) expect(point_groups_spinach?(device)).to be_kind_of(PointGroup) expect(point_groups_broccoli?(device)).to be_kind_of(PointGroup) expect(point_groups_beet?(device)).to be_kind_of(PointGroup) + expect(settings_three_d_garden?(device)).to be(true) end - it "seeds accounts with demo account data: XL" do + it "seeds accounts with demo account data: genesis XL" do start_tests "genesis_xl_1.7", true, true expect(plant_count?(device)).to eq(253) @@ -1484,6 +1662,15 @@ def check_slot_pairing(slot, expected_name) expect(point_groups_beet?(device)).to be_kind_of(PointGroup) end + it "seeds accounts with demo account data: express XL" do + start_tests "express_xl_1.2", true, true + + expect(plant_count?(device)).to eq(188) + expect(point_groups_spinach?(device)).to be_kind_of(PointGroup) + expect(point_groups_broccoli?(device)).to be_kind_of(PointGroup) + expect(point_groups_beet?(device)).to be_kind_of(PointGroup) + end + it "seeds accounts when sequence versions not available: demo account" do start_tests "genesis_1.7", false, true @@ -1514,6 +1701,18 @@ def check_slot_pairing(slot, expected_name) expect(sequences_mow_all_weeds?(device)).to be_kind_of(Sequence) end + it "seeds accounts when sequence versions not available: Genesis XL 1.8" do + start_tests "genesis_xl_1.8", false + + expect(sequences_mow_all_weeds?(device)).to be_kind_of(Sequence) + end + + it "seeds accounts when sequence versions not available: Genesis 1.8" do + start_tests "genesis_1.8", false + + expect(sequences_mow_all_weeds?(device)).to be_kind_of(Sequence) + end + it "does not seed accounts" do start_tests "none"