diff --git a/Gemfile.lock b/Gemfile.lock index 7820d7c878..1be0396403 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -119,7 +119,7 @@ GEM factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - faker (3.2.1) + faker (3.2.2) i18n (>= 1.8.11, < 2) faraday (1.10.3) faraday-em_http (~> 1.0) @@ -221,7 +221,7 @@ GEM multipart-post (2.3.0) mutations (0.9.1) activesupport - net-imap (0.4.2) + net-imap (0.4.4) date net-protocol net-pop (0.1.2) @@ -251,7 +251,7 @@ GEM faraday_middleware (~> 1.2) hashie (~> 4.1) multi_json (~> 1.15) - racc (1.7.1) + racc (1.7.2) rack (2.2.8) rack-attack (6.7.0) rack (>= 1.0, < 4) @@ -292,7 +292,7 @@ GEM method_source rake (>= 12.2) thor (~> 1.0) - rake (13.0.6) + rake (13.1.0) rbtree (0.4.6) redis (4.8.1) representable (3.2.0) diff --git a/app/controllers/api/ais_controller.rb b/app/controllers/api/ais_controller.rb index 22a56642da..901094c2fb 100644 --- a/app/controllers/api/ais_controller.rb +++ b/app/controllers/api/ais_controller.rb @@ -111,8 +111,10 @@ def make_request(system_prompt, user_prompt, stream) url = "https://api.openai.com/v1/chat/completions" context_key = raw_json[:context_key] lua_request = context_key == "lua" + model_lua = ENV["OPENAI_MODEL_LUA"] || "gpt-3.5-turbo-16k" + model_other = ENV["OPENAI_MODEL_OTHER"] || "gpt-3.5-turbo" payload = { - "model" => lua_request ? "gpt-3.5-turbo-16k" : "gpt-3.5-turbo", + "model" => lua_request ? model_lua : model_other, "messages" => [ {role: "system", content: system_prompt}, {role: "user", content: user_prompt}, @@ -131,9 +133,11 @@ def make_request(system_prompt, user_prompt, stream) full = "" response = conn.post("") do |req| req.body = payload + buffer = "" total = 0 missed = false req.options.on_data = Proc.new do |chunk, size| + buffer += chunk total += chunk.bytes.length diff = size - total if (diff != 0 && !missed) || Rails.env.test? @@ -142,9 +146,12 @@ def make_request(system_prompt, user_prompt, stream) current_device.tell("Response stream incomplete.", ["toast"], "warn") missed = true end - data_strings = chunk.split("data: ")[1,999] - for data_str in data_strings - data = JSON.parse(data_str) + boundary = buffer.index("\n\n") + while not boundary.nil? + data_str = buffer.slice(0, boundary) + buffer = buffer.slice(boundary + 2, buffer.length) + json_string = data_str.split("data: ")[1] + data = JSON.parse(json_string) output = data["choices"][0] if output["finish_reason"].nil? content = output["delta"].dig("content") || "" @@ -157,6 +164,7 @@ def make_request(system_prompt, user_prompt, stream) stream.close return {} end + boundary = buffer.index("\n\n") end end end diff --git a/app/controllers/api/demo_accounts_controller.rb b/app/controllers/api/demo_accounts_controller.rb index 7518fd4900..4b47ddf546 100644 --- a/app/controllers/api/demo_accounts_controller.rb +++ b/app/controllers/api/demo_accounts_controller.rb @@ -9,7 +9,10 @@ def create private def create_params - @create_params ||= { secret: raw_json.fetch(:secret) } + @create_params ||= { + secret: raw_json.fetch(:secret), + product_line: raw_json.fetch(:product_line), + } end end end diff --git a/app/models/fbos_config.rb b/app/models/fbos_config.rb index 1b868a3f76..22e5ab2cb7 100644 --- a/app/models/fbos_config.rb +++ b/app/models/fbos_config.rb @@ -12,7 +12,9 @@ class MissingSerial < StandardError; end FARMDUINO_K14 = "farmduino_k14", FARMDUINO_K15 = "farmduino_k15", FARMDUINO_K16 = "farmduino_k16", + FARMDUINO_K17 = "farmduino_k17", EXPRESS_K10 = "express_k10", EXPRESS_K11 = "express_k11", + EXPRESS_K12 = "express_k12", ] end diff --git a/app/mutations/devices/create_seed_data.rb b/app/mutations/devices/create_seed_data.rb index fde0f20387..5afd533b92 100644 --- a/app/mutations/devices/create_seed_data.rb +++ b/app/mutations/devices/create_seed_data.rb @@ -3,19 +3,22 @@ class CreateSeedData < Mutations::Command PRODUCT_LINES = { "express_1.0" => Devices::Seeders::ExpressOneZero, "express_1.1" => Devices::Seeders::ExpressOneOne, + "express_1.2" => Devices::Seeders::ExpressOneTwo, "express_xl_1.0" => Devices::Seeders::ExpressXlOneZero, "express_xl_1.1" => Devices::Seeders::ExpressXlOneOne, + "express_xl_1.2" => Devices::Seeders::ExpressXlOneTwo, "genesis_1.2" => Devices::Seeders::GenesisOneTwo, "genesis_1.3" => Devices::Seeders::GenesisOneThree, "genesis_1.4" => Devices::Seeders::GenesisOneFour, "genesis_1.5" => Devices::Seeders::GenesisOneFive, "genesis_1.6" => Devices::Seeders::GenesisOneSix, + "genesis_1.7" => Devices::Seeders::GenesisOneSeven, "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, - "demo_account" => Devices::Seeders::DemoAccountSeeder, "none" => Devices::Seeders::None, } @@ -24,6 +27,10 @@ class CreateSeedData < Mutations::Command string :product_line, in: PRODUCT_LINES.keys end + optional do + boolean :demo + end + def execute self.delay.run_seeds! { done: "Loading resources now." } @@ -37,6 +44,10 @@ def run_seeds! seeder.class::COMMAND_ORDER.map do |cmd| seeder.send(cmd) end + + if demo + Devices::Seeders::DemoAccountSeeder.new(device).misc(product_line) + end end end end diff --git a/app/mutations/devices/seeders/demo_account_seeder.rb b/app/mutations/devices/seeders/demo_account_seeder.rb index d28fbe828d..bafecbd7da 100644 --- a/app/mutations/devices/seeders/demo_account_seeder.rb +++ b/app/mutations/devices/seeders/demo_account_seeder.rb @@ -1,6 +1,6 @@ module Devices module Seeders - class DemoAccountSeeder < ExpressOneZero + class DemoAccountSeeder < AbstractSeeder BASE_URL = "/app-resources/img/demo_accounts/" FEEDS = { "Express XL" => "Express_XL_Demo_Webcam.JPG", @@ -10,14 +10,19 @@ class DemoAccountSeeder < ExpressOneZero } UNUSED_ALERTS = ["api.seed_data.missing", "api.user.not_welcomed"] - def webcam_feeds - # device.webcam_feeds.destroy_all! - FEEDS.map do |(name, url)| - p = { name: name, - url: (BASE_URL + url), - device: device } - WebcamFeeds::Create.run!(p) - end + def feed(product_line) + feed_name = "" + feed_name += "Genesis" if product_line.include?("genesis") + feed_name += "Express" if product_line.include?("express") + feed_name += " XL" if product_line.include?("xl") + feed_name + end + + def create_webcam_feed(product_line) + feed_name = feed(product_line) + WebcamFeeds::Create.run!({ name: feed_name, + url: BASE_URL + FEEDS[feed_name], + device: device }) end def plants @@ -69,7 +74,13 @@ def point_groups_beet # tester FBOS version `1000.0.0`. READ_COMMENT_ABOVE = "100.0.0" - def misc + def misc(product_line) + create_webcam_feed(product_line) + plants + point_groups_spinach + point_groups_broccoli + point_groups_beet + device.alerts.where(problem_tag: UNUSED_ALERTS).destroy_all DEMO_ALERTS .map { |p| p.merge(device: device) } diff --git a/app/mutations/devices/seeders/express_one_two.rb b/app/mutations/devices/seeders/express_one_two.rb new file mode 100644 index 0000000000..e99fb3a97e --- /dev/null +++ b/app/mutations/devices/seeders/express_one_two.rb @@ -0,0 +1,11 @@ +module Devices + module Seeders + class ExpressOneTwo < AbstractExpress + def settings_firmware + device + .fbos_config + .update!(firmware_hardware: FbosConfig::EXPRESS_K12) + end + 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 new file mode 100644 index 0000000000..3c795d60a1 --- /dev/null +++ b/app/mutations/devices/seeders/express_xl_one_two.rb @@ -0,0 +1,23 @@ +module Devices + module Seeders + class ExpressXlOneTwo < AbstractExpress + def settings_device_name + device.update!(name: Names::EXPRESS_XL) + end + + def settings_firmware + device + .fbos_config + .update!(firmware_hardware: FbosConfig::EXPRESS_K12) + end + + def settings_default_map_size_x + device.web_app_config.update!(map_size_x: 6_000) + end + + def settings_default_map_size_y + device.web_app_config.update!(map_size_y: 2_400) + end + end + end +end diff --git a/app/mutations/devices/seeders/genesis_one_seven.rb b/app/mutations/devices/seeders/genesis_one_seven.rb new file mode 100644 index 0000000000..2502fd6760 --- /dev/null +++ b/app/mutations/devices/seeders/genesis_one_seven.rb @@ -0,0 +1,77 @@ +module Devices + module Seeders + class GenesisOneSeven < AbstractGenesis + def settings_firmware + device + .fbos_config + .update!(firmware_hardware: FbosConfig::FARMDUINO_K17) + end + + def settings_change_firmware_config_defaults + 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: 50, + y: 700, + z: -200, + tool: tools_rotary) + end + + def tool_slots_slot_7 + add_tool_slot(name: ToolNames::SEED_TROUGH_1, + x: 0, + y: 25, + z: -100, + tool: tools_seed_trough_1, + pullout_direction: ToolSlot::NONE, + gantry_mounted: true) + end + + def tool_slots_slot_8 + add_tool_slot(name: ToolNames::SEED_TROUGH_2, + x: 0, + y: 50, + z: -100, + 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_seven.rb b/app/mutations/devices/seeders/genesis_xl_one_seven.rb new file mode 100644 index 0000000000..9ff9cdfb70 --- /dev/null +++ b/app/mutations/devices/seeders/genesis_xl_one_seven.rb @@ -0,0 +1,89 @@ +module Devices + module Seeders + class GenesisXlOneSeven < AbstractGenesis + def settings_firmware + device + .fbos_config + .update!(firmware_hardware: FbosConfig::FARMDUINO_K17) + end + + def settings_change_firmware_config_defaults + 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_900) + 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: 50, + y: 700, + z: -200, + tool: tools_rotary) + end + + def tool_slots_slot_7 + add_tool_slot(name: ToolNames::SEED_TROUGH_1, + x: 0, + y: 25, + z: -100, + tool: tools_seed_trough_1, + pullout_direction: ToolSlot::NONE, + gantry_mounted: true) + end + + def tool_slots_slot_8 + add_tool_slot(name: ToolNames::SEED_TROUGH_2, + x: 0, + y: 50, + z: -100, + 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/users/create_demo.rb b/app/mutations/users/create_demo.rb index abad1854c0..9e32493505 100644 --- a/app/mutations/users/create_demo.rb +++ b/app/mutations/users/create_demo.rb @@ -1,6 +1,9 @@ module Users class CreateDemo < Mutations::Command - required { string :secret } + required do + string :secret + string :product_line + end def execute self.delay.doing_it_asap @@ -41,7 +44,8 @@ def update_fields def seed_user Devices::CreateSeedData.run!(device: user.device, - product_line: "demo_account") + product_line: product_line, + demo: true) end def broadcast_the_token diff --git a/app/views/dashboard/demo.html.erb b/app/views/dashboard/demo.html.erb index 8fd0d45c3d..0da75730f7 100644 --- a/app/views/dashboard/demo.html.erb +++ b/app/views/dashboard/demo.html.erb @@ -35,7 +35,7 @@ background-color: #489fe4; border: none; border-radius: 5px; - bottom: 30px; + bottom: 60px; box-shadow: 0 0 15px rgba(0,0,0,0.4); color: #fff; font-family: "Cabin", sans-serif; @@ -45,6 +45,11 @@ position: absolute; } +.demo-options { + position: absolute; + bottom: 15px; +} + .demo-button:hover { background-color: #287dc1; cursor: pointer; diff --git a/example.env b/example.env index 2e24465c21..a479371b3b 100644 --- a/example.env +++ b/example.env @@ -161,3 +161,7 @@ OPENAI_API_KEY= # OpenAI API sampling temperature. Optional. Float between 0 and 2. # Defaults to 1, use a lower value for less random output. OPENAI_API_TEMPERATURE= +# OpenAI model name for Lua code generation requests. +OPENAI_MODEL_LUA= +# OpenAI model name for other requests. +OPENAI_MODEL_OTHER= diff --git a/frontend/apology.tsx b/frontend/apology.tsx index 963f275655..1c97844703 100644 --- a/frontend/apology.tsx +++ b/frontend/apology.tsx @@ -27,7 +27,7 @@ export function Apology(_: {}) {

Page Error

{"We can't render this part of the page due to an unrecoverable error."} - Here are some things you can try: +  Here are some things you can try:
  1. @@ -35,7 +35,7 @@ export function Apology(_: {}) {
  2. {"Perform a \"hard refresh\""} - (CTRL + SHIFT + R on most machines). +  (CTRL + SHIFT + R on most machines).
  3. @@ -48,8 +48,8 @@ export function Apology(_: {}) {
  4. Send a report to our developer team via the  - FarmBot software - forum. Including additional information (such as steps leading up + FarmBot software forum. + Including additional information (such as steps leading up to the error) helps us identify solutions more quickly.
  5. diff --git a/frontend/constants.ts b/frontend/constants.ts index 17351b3d95..6c2c8f1438 100644 --- a/frontend/constants.ts +++ b/frontend/constants.ts @@ -1919,7 +1919,7 @@ export namespace SetupWizardContent { export const CHECK_CAMERA_CABLE = trim(`Check that the camera is plugged in correctly to either a Raspberry Pi USB port (Express v1.0 kits and all Genesis kits) or into - one of the vertically oriented USB ports on the Farmduino (Express v1.1 + one of the vertically oriented USB ports on the Farmduino (Express v1.1+ kits). For cameras with a 90 degree intermediary connector at the cross-slide, ensure the connectors are fully connected.`); diff --git a/frontend/controls/peripherals/__tests__/index_test.tsx b/frontend/controls/peripherals/__tests__/index_test.tsx index f5cbae53c1..798aed31ac 100644 --- a/frontend/controls/peripherals/__tests__/index_test.tsx +++ b/frontend/controls/peripherals/__tests__/index_test.tsx @@ -82,8 +82,10 @@ describe("", () => { ["farmduino_k14", 5], ["farmduino_k15", 5], ["farmduino_k16", 7], + ["farmduino_k17", 7], ["express_k10", 3], ["express_k11", 3], + ["express_k12", 3], ])("adds peripherals: %s", (firmware, expectedAdds) => { const p = fakeProps(); p.firmwareHardware = firmware; diff --git a/frontend/controls/peripherals/index.tsx b/frontend/controls/peripherals/index.tsx index 803ac71939..69f93173e1 100644 --- a/frontend/controls/peripherals/index.tsx +++ b/frontend/controls/peripherals/index.tsx @@ -93,6 +93,7 @@ export class Peripherals ...EXTRA_PERIPHERALS, ]; case "farmduino_k16": + case "farmduino_k17": return [ ...BASE_PERIPHERALS, ...ROTARY_TOOL, @@ -100,6 +101,7 @@ export class Peripherals ]; case "express_k10": case "express_k11": + case "express_k12": return BASE_PERIPHERALS; } } diff --git a/frontend/css/farm_designer/farm_designer_panels.scss b/frontend/css/farm_designer/farm_designer_panels.scss index 121a1492a9..3a5c58f4de 100644 --- a/frontend/css/farm_designer/farm_designer_panels.scss +++ b/frontend/css/farm_designer/farm_designer_panels.scss @@ -3020,6 +3020,24 @@ li { height: 3rem; } } + .resolution-change-warning { + i { + color: $darkest_red; + margin-right: 1rem; + } + p { + display: inline; + margin-right: 0.5rem; + font-size: 1.2rem; + } + .click { + cursor: pointer; + font-weight: bold; + &:hover { + color: $black; + } + } + } } .update-take-photo { margin-top: 1rem; diff --git a/frontend/demo/__tests__/demo_iframe_test.tsx b/frontend/demo/__tests__/demo_iframe_test.tsx index d201a33de8..346363e9e4 100644 --- a/frontend/demo/__tests__/demo_iframe_test.tsx +++ b/frontend/demo/__tests__/demo_iframe_test.tsx @@ -43,6 +43,13 @@ describe("", () => { expect(console.error).toHaveBeenCalledWith(mockResponse); }); + it("changes model", () => { + const wrapper = shallow(); + expect(wrapper.state().productLine).toEqual("genesis_1.7"); + wrapper.find("FBSelect").simulate("change", { value: "express_1.2" }); + expect(wrapper.state().productLine).toEqual("express_1.2"); + }); + it("handles MQTT messages", () => { const el = shallow(); el.instance().handleMessage("foo", Buffer.from("bar")); diff --git a/frontend/demo/demo_iframe.tsx b/frontend/demo/demo_iframe.tsx index cfb2a26552..21f5009809 100644 --- a/frontend/demo/demo_iframe.tsx +++ b/frontend/demo/demo_iframe.tsx @@ -6,10 +6,13 @@ import { ExternalUrl } from "../external_urls"; import { t } from "../i18next_wrapper"; import { tourPath } from "../help/tours"; import { Path } from "../internal_urls"; +import { FBSelect } from "../ui"; +import { SEED_DATA_OPTIONS, SEED_DATA_OPTIONS_DDI } from "../messages/cards"; interface State { error: Error | undefined; stage: string; + productLine: string; } const WS_CONFIG = { @@ -25,8 +28,11 @@ export const WAITING_ON_API = "Planting your demo garden..."; // APPLICATION CODE ============================== export class DemoIframe extends React.Component<{}, State> { - state: State = - { error: undefined, stage: t("DEMO THE APP") }; + state: State = { + error: undefined, + stage: t("DEMO THE APP"), + productLine: "genesis_1.7", + }; setError = (error?: Error) => this.setState({ error }); @@ -44,7 +50,10 @@ export class DemoIframe extends React.Component<{}, State> { is51 && this.setState({ stage: EASTER_EGG }); return axios - .post(HTTP_URL, { secret: SECRET }) + .post(HTTP_URL, { + secret: SECRET, + product_line: this.state.productLine, + }) .then(() => this.setState({ stage: WAITING_ON_API })) .catch(this.setError); }; @@ -61,7 +70,7 @@ export class DemoIframe extends React.Component<{}, State> { }; ok = () => { - + const selection = this.state.productLine; return
    ; }; diff --git a/frontend/devices/connectivity/fbos_metric_demo_data.ts b/frontend/devices/connectivity/fbos_metric_demo_data.ts index 1c139b5dd8..c65a90bd95 100644 --- a/frontend/devices/connectivity/fbos_metric_demo_data.ts +++ b/frontend/devices/connectivity/fbos_metric_demo_data.ts @@ -21,7 +21,7 @@ export const generateDemoTelemetry = (): TaggedTelemetry[] => { cpu_usage: 5 + random(-4, 4), target: "demo", fbos_version: "100.0.0", - firmware_hardware: "farmduino_k16", + firmware_hardware: "farmduino_k17", created_at: at, updated_at: (new Date(at)).toISOString(), } diff --git a/frontend/devices/interfaces.ts b/frontend/devices/interfaces.ts index e4e4693e4a..44ef7d0d06 100644 --- a/frontend/devices/interfaces.ts +++ b/frontend/devices/interfaces.ts @@ -35,10 +35,12 @@ export enum Feature { endstop_invert = "endstop_invert", express_k10 = "express_k10", express_k11 = "express_k11", + express_k12 = "express_k12", express_stall_detection = "express_stall_detection", farmduino_k14 = "farmduino_k14", farmduino_k15 = "farmduino_k15", farmduino_k16 = "farmduino_k16", + farmduino_k17 = "farmduino_k17", firmware_restart = "firmware_restart", flash_firmware = "flash_firmware", groups = "groups", diff --git a/frontend/messages/__tests__/cards_test.tsx b/frontend/messages/__tests__/cards_test.tsx index 2e9cab3ff1..8bcabf6678 100644 --- a/frontend/messages/__tests__/cards_test.tsx +++ b/frontend/messages/__tests__/cards_test.tsx @@ -2,6 +2,11 @@ jest.mock("../../devices/actions", () => ({ updateConfig: jest.fn() })); jest.mock("../../api/crud", () => ({ destroy: jest.fn() })); +let mockFeatureBoolean = false; +jest.mock("../../devices/should_display", () => ({ + shouldDisplayFeature: () => mockFeatureBoolean, +})); + const fakeBulletin: Bulletin = { content: "Alert content.", href: "https://farm.bot", @@ -28,7 +33,9 @@ jest.mock("../../redux/store", () => ({ import React from "react"; import { mount, shallow } from "enzyme"; -import { AlertCard, changeFirmwareHardware, ReSeedAccount } from "../cards"; +import { + AlertCard, changeFirmwareHardware, ReSeedAccount, SEED_DATA_OPTIONS, +} from "../cards"; import { AlertCardProps, Bulletin } from "../interfaces"; import { fakeTimeSettings } from "../../__test_support__/fake_time_settings"; import { FBSelect } from "../../ui"; @@ -249,6 +256,18 @@ describe("changeFirmwareHardware()", () => { }); }); +describe("SEED_DATA_OPTIONS()", () => { + it("returns options", () => { + mockFeatureBoolean = false; + expect(SEED_DATA_OPTIONS().length).toEqual(13); + }); + + it("returns more options", () => { + mockFeatureBoolean = true; + expect(SEED_DATA_OPTIONS().length).toEqual(17); + }); +}); + describe("", () => { it("changes selection", () => { window.confirm = () => true; diff --git a/frontend/messages/cards.tsx b/frontend/messages/cards.tsx index 7105efd116..eeecdcd4b6 100644 --- a/frontend/messages/cards.tsx +++ b/frontend/messages/cards.tsx @@ -32,6 +32,8 @@ import { push } from "../history"; import moment from "moment"; import { Path } from "../internal_urls"; import { logout } from "../logout"; +import { shouldDisplayFeature } from "../devices/should_display"; +import { Feature } from "../devices/interfaces"; export const AlertCard = (props: AlertCardProps) => { const { alert, timeSettings, findApiAlertById, dispatch } = props; @@ -187,6 +189,11 @@ const FirmwareChoiceTable = () => {"Farmduino"} {FIRMWARE_CHOICES_DDI["farmduino_k16"].label} + + {"Genesis v1.7"} + {"Farmduino"} + {FIRMWARE_CHOICES_DDI["farmduino_k17"].label} + {"Express v1.0"} {"Farmduino"} @@ -197,6 +204,11 @@ const FirmwareChoiceTable = () => {"Farmduino"} {FIRMWARE_CHOICES_DDI["express_k11"].label} + + {"Express v1.2"} + {"Farmduino"} + {FIRMWARE_CHOICES_DDI["express_k12"].label} + ; @@ -239,25 +251,37 @@ const FirmwareMissing = (props: FirmwareMissingProps) => ; -export const SEED_DATA_OPTIONS = (): DropDownItem[] => [ +export const SEED_DATA_OPTIONS = (displayAll = false): DropDownItem[] => [ { label: "Genesis v1.2", value: "genesis_1.2" }, { label: "Genesis v1.3", value: "genesis_1.3" }, { label: "Genesis v1.4", value: "genesis_1.4" }, { label: "Genesis v1.5", value: "genesis_1.5" }, { label: "Genesis v1.6", value: "genesis_1.6" }, + ...((shouldDisplayFeature(Feature.farmduino_k17) || displayAll) + ? [{ label: "Genesis v1.7", value: "genesis_1.7" }] + : []), { label: "Genesis v1.4 XL", value: "genesis_xl_1.4" }, { label: "Genesis v1.5 XL", value: "genesis_xl_1.5" }, { label: "Genesis v1.6 XL", value: "genesis_xl_1.6" }, + ...((shouldDisplayFeature(Feature.farmduino_k17) || displayAll) + ? [{ label: "Genesis v1.7 XL", value: "genesis_xl_1.7" }] + : []), { label: "Express v1.0", value: "express_1.0" }, { label: "Express v1.1", value: "express_1.1" }, + ...((shouldDisplayFeature(Feature.express_k12) || displayAll) + ? [{ label: "Express v1.2", value: "express_1.2" }] + : []), { label: "Express v1.0 XL", value: "express_xl_1.0" }, { label: "Express v1.1 XL", value: "express_xl_1.1" }, + ...((shouldDisplayFeature(Feature.express_k12) || displayAll) + ? [{ label: "Express v1.2 XL", value: "express_xl_1.2" }] + : []), { label: "Custom Bot", value: "none" }, ]; export const SEED_DATA_OPTIONS_DDI = (): Record => { const options: Record = {}; - SEED_DATA_OPTIONS().map(ddi => options[ddi.value] = ddi); + SEED_DATA_OPTIONS(true).map(ddi => options[ddi.value] = ddi); return options; }; diff --git a/frontend/os_download/__tests__/content_test.tsx b/frontend/os_download/__tests__/content_test.tsx index b3b467070b..12cb7f5f22 100644 --- a/frontend/os_download/__tests__/content_test.tsx +++ b/frontend/os_download/__tests__/content_test.tsx @@ -50,7 +50,7 @@ describe("", () => { it("runs the wizard: express", () => { const wrapper = mount(); clickButton(wrapper, 1, "express", { partial_match: true }); - clickButton(wrapper, 0, "express v1.0"); + clickButton(wrapper, 1, "express v1.0"); expect(wrapper.text().toLowerCase()).toContain("zero"); }); diff --git a/frontend/os_download/content.tsx b/frontend/os_download/content.tsx index 8545f6813d..27d351ff85 100644 --- a/frontend/os_download/content.tsx +++ b/frontend/os_download/content.tsx @@ -55,7 +55,9 @@ const PLATFORM_DATA = (): PlatformContent[] => [ "Genesis XL v1.5", "Genesis XL v1.6 (black cable)", "Express v1.1 (USB)", + "Express v1.2 (USB)", "Express XL v1.1 (USB)", + "Express XL v1.2 (USB)", ], }, { @@ -67,6 +69,8 @@ const PLATFORM_DATA = (): PlatformContent[] => [ "Genesis XL v1.6.1 (white cable)", "Genesis v1.6.2 (white cable or 2 HDMI ports)", "Genesis XL v1.6.2 (white cable or 2 HDMI ports)", + "Genesis v1.7", + "Genesis XL v1.7", ], }, { @@ -135,14 +139,17 @@ enum Version { "v1.4" = "v1.4", "v1.5" = "v1.5", "v1.6" = "v1.6", + "v1.7" = "v1.7", } const VERSIONS = () => ({ [Model.Express]: [ - Version["v1.0"], + // Version["v1.2"], Version["v1.1"], + Version["v1.0"], ], [Model.Genesis]: [ + // Version["v1.7"], Version["v1.6"], Version["v1.5"], Version["v1.4"], @@ -204,6 +211,9 @@ const DOWNLOADS = (): Downloads => ({ [Version["v1.1"]]: { [Run.first]: RPIZ2(), }, + [Version["v1.2"]]: { + [Run.first]: RPIZ2(), + }, }, [Model.Genesis]: { [Version["v1.2"]]: { @@ -223,6 +233,9 @@ const DOWNLOADS = (): Downloads => ({ [Run.second]: RPI4(), [Run.third]: RPI4(), }, + [Version["v1.7"]]: { + [Run.first]: RPI4(), + }, } }); diff --git a/frontend/photos/capture_settings/__tests__/capture_size_selection_test.tsx b/frontend/photos/capture_settings/__tests__/capture_size_selection_test.tsx index 39ee972379..9e249816fc 100644 --- a/frontend/photos/capture_settings/__tests__/capture_size_selection_test.tsx +++ b/frontend/photos/capture_settings/__tests__/capture_size_selection_test.tsx @@ -1,9 +1,71 @@ import React from "react"; import { shallow } from "enzyme"; -import { CaptureSizeSelection } from "../capture_size_selection"; +import { + CaptureSizeSelection, PhotoResolutionSettingChanged, +} from "../capture_size_selection"; import { CaptureSizeSelectionProps } from "../interfaces"; import { FBSelect } from "../../../ui"; +describe("", () => { + const fakeProps = (): CaptureSizeSelectionProps => ({ + env: {}, + saveFarmwareEnv: jest.fn(), + dispatch: jest.fn(), + }); + + it("doesn't display warning: not calibrated", () => { + const p = fakeProps(); + p.env = { + take_photo_width: "200", + take_photo_height: "100", + }; + const wrapper = shallow(); + expect(wrapper.find("i").length).toEqual(0); + }); + + it("doesn't display warning: not changed", () => { + const p = fakeProps(); + p.env = { + take_photo_width: "200", + take_photo_height: "100", + CAMERA_CALIBRATION_center_pixel_location_x: "100", + CAMERA_CALIBRATION_center_pixel_location_y: "50", + }; + const wrapper = shallow(); + expect(wrapper.find("i").length).toEqual(0); + expect(wrapper.find(".click").length).toEqual(0); + }); + + it("doesn't display revert option", () => { + const p = fakeProps(); + p.env = { + take_photo_width: "200", + take_photo_height: "100", + CAMERA_CALIBRATION_center_pixel_location_x: "1", + CAMERA_CALIBRATION_center_pixel_location_y: "50", + }; + const wrapper = shallow(); + expect(wrapper.find("i").length).toEqual(1); + expect(wrapper.find(".click").length).toEqual(0); + }); + + it("changes value", () => { + const p = fakeProps(); + p.env = { + take_photo_width: "200", + take_photo_height: "100", + CAMERA_CALIBRATION_center_pixel_location_x: "320", + CAMERA_CALIBRATION_center_pixel_location_y: "50", + }; + const wrapper = shallow(); + expect(wrapper.find("i").length).toEqual(1); + expect(wrapper.find(".click").length).toEqual(1); + wrapper.find(".click").simulate("click"); + expect(p.saveFarmwareEnv).toHaveBeenCalledWith("take_photo_width", "640"); + expect(p.saveFarmwareEnv).toHaveBeenCalledWith("take_photo_height", "480"); + }); +}); + describe("", () => { const fakeProps = (): CaptureSizeSelectionProps => ({ env: {}, diff --git a/frontend/photos/capture_settings/capture_size_selection.tsx b/frontend/photos/capture_settings/capture_size_selection.tsx index 29ff650210..1d32c14b97 100644 --- a/frontend/photos/capture_settings/capture_size_selection.tsx +++ b/frontend/photos/capture_settings/capture_size_selection.tsx @@ -7,6 +7,51 @@ import { Camera, parseCameraSelection } from "./camera_selection"; import { getModifiedClassNameSpecifyDefault } from "../../settings/default_values"; import { Highlight } from "../../settings/maybe_highlight"; import { Path } from "../../internal_urls"; +import { UserEnv } from "../../devices/interfaces"; + +const getCurrentSizeSetting = + (env: UserEnv): Record<"width" | "height", string> => ({ + width: env["take_photo_width"] || "640", + height: env["take_photo_height"] || "480", + }); + +export const PhotoResolutionSettingChanged = + (props: CaptureSizeSelectionProps) => { + const { env, dispatch, saveFarmwareEnv } = props; + const { width, height } = getCurrentSizeSetting(env); + const selectedLongEdge = Math.max(parseInt(width), parseInt(height)); + const calibratedX = env["CAMERA_CALIBRATION_center_pixel_location_x"]; + const calibratedY = env["CAMERA_CALIBRATION_center_pixel_location_y"]; + const calibratedWidth = calibratedX && parseInt(calibratedX) * 2; + const calibratedHeight = calibratedY && parseInt(calibratedY) * 2; + const calibratedLongEdge = calibratedWidth && calibratedHeight && + Math.max(calibratedWidth, calibratedHeight); + const sizeDifference = calibratedLongEdge && + calibratedLongEdge != selectedLongEdge; + const options = SIZE_OPTIONS(parseCameraSelection(env)); + const valueGuess = Object.values(options).map(ddi => ddi.value) + .filter(value => ("" + value).startsWith("" + calibratedLongEdge))[0]; + const ddiGuess = options[valueGuess as Size]; + const sizeGuess = SIZES[valueGuess as Size]; + + return
    + {sizeDifference && } + {sizeDifference &&

    + {t("The camera was previously calibrated for a different resolution.")} +

    } + {sizeDifference &&

    + {t("Calibrate the camera again for the selected resolution.")} +

    } + {sizeDifference && ddiGuess &&

    { + dispatch(saveFarmwareEnv("take_photo_width", "" + sizeGuess.width)); + dispatch(saveFarmwareEnv("take_photo_height", "" + sizeGuess.height)); + }}> + {t("Alternatively, revert to the previous resolution")}: {ddiGuess.label}. +

    } +
    ; + }; export class CaptureSizeSelection extends React.Component { @@ -14,9 +59,9 @@ export class CaptureSizeSelection render() { const { env, dispatch, saveFarmwareEnv } = this.props; - const width = env["take_photo_width"] || "640"; - const height = env["take_photo_height"] || "480"; + const { width, height } = getCurrentSizeSetting(env); const selectedSize = getSelection(width, height); + return
    @@ -47,6 +92,7 @@ export class CaptureSizeSelection }} /> + {(this.state.custom || selectedSize.value == Size.custom) &&
    @@ -100,26 +146,30 @@ enum Size { const MAXIMUM = () => ({ label: t("Maximum"), value: Size.maximum }); const CUSTOM = () => ({ label: t("Custom"), value: Size.custom }); -const SIZE_OPTIONS = ( - camera = Camera.RPI, -): Record => ({ - [Size.maximum]: MAXIMUM(), - [Size.custom]: CUSTOM(), - [Size.r320x240]: { label: t("320 x 240 (0.08MP)"), value: Size.r320x240 }, - [Size.r640x480]: { label: t("640 x 480 (0.3MP)"), value: Size.r640x480 }, - [Size.r800x600]: { label: t("800 x 600 (0.5MP)"), value: Size.r800x600 }, - [Size.r1280x960]: { label: t("1280 x 960 (1.3MP)"), value: Size.r1280x960 }, - [Size.r1600x1200]: { label: t("1600 x 1200 (2MP)"), value: Size.r1600x1200 }, - [Size.r2592x1944]: camera == Camera.RPI - ? { label: t("2592 x 1944 (5MP)"), value: Size.r2592x1944 } - : undefined, - [Size.r3280x2464]: camera == Camera.RPI - ? { label: t("3280 x 2464 (8MP)"), value: Size.r3280x2464 } - : undefined, - [Size.r4056x3040]: camera == Camera.RPI - ? { label: t("4056 x 3040 (12.3MP)"), value: Size.r4056x3040 } - : undefined, -}); +type Options = Partial>; +const SIZE_OPTIONS = (camera = Camera.RPI): Options => { + const OPTIONS: Options = { + [Size.maximum]: MAXIMUM(), + [Size.custom]: CUSTOM(), + [Size.r320x240]: { label: t("320 x 240 (0.08MP)"), value: Size.r320x240 }, + [Size.r640x480]: { label: t("640 x 480 (0.3MP)"), value: Size.r640x480 }, + [Size.r800x600]: { label: t("800 x 600 (0.5MP)"), value: Size.r800x600 }, + [Size.r1280x960]: { label: t("1280 x 960 (1.3MP)"), value: Size.r1280x960 }, + [Size.r1600x1200]: { label: t("1600 x 1200 (2MP)"), value: Size.r1600x1200 }, + }; + if (camera == Camera.RPI) { + OPTIONS[Size.r2592x1944] = { + label: t("2592 x 1944 (5MP)"), value: Size.r2592x1944, + }; + OPTIONS[Size.r3280x2464] = { + label: t("3280 x 2464 (8MP)"), value: Size.r3280x2464, + }; + OPTIONS[Size.r4056x3040] = { + label: t("4056 x 3040 (12.3MP)"), value: Size.r4056x3040, + }; + } + return OPTIONS; +}; const SIZES: Record> = { [Size.maximum]: { width: 10000, height: 10000 }, diff --git a/frontend/routes.tsx b/frontend/routes.tsx index 976fe3b334..68e38040bc 100644 --- a/frontend/routes.tsx +++ b/frontend/routes.tsx @@ -66,7 +66,10 @@ export class RootComponent - {ChildRoute && } + {ChildRoute && + + + } diff --git a/frontend/settings/fbos_settings/__tests__/fbos_details_test.tsx b/frontend/settings/fbos_settings/__tests__/fbos_details_test.tsx index 99d55c7023..8ef5933466 100644 --- a/frontend/settings/fbos_settings/__tests__/fbos_details_test.tsx +++ b/frontend/settings/fbos_settings/__tests__/fbos_details_test.tsx @@ -296,6 +296,7 @@ describe("", () => { ["Zero W", "rpi", "arduino"], ["3", "rpi3", "arduino"], ["Zero 2 W", "rpi3", "express_k11"], + ["Zero 2 W", "rpi3", "express_k12"], ["4", "rpi4", "arduino"], ["Unknown", "", undefined], ])("returns correct pi model: %s", (expected, chip, firmware) => { diff --git a/frontend/settings/fbos_settings/__tests__/rpi_model_test.tsx b/frontend/settings/fbos_settings/__tests__/rpi_model_test.tsx index 52a53c8f4e..e5f386bc3c 100644 --- a/frontend/settings/fbos_settings/__tests__/rpi_model_test.tsx +++ b/frontend/settings/fbos_settings/__tests__/rpi_model_test.tsx @@ -19,8 +19,10 @@ type TestCase = [string, string, FirmwareHardware, string]; const TEST_CASES: TestCase[] = [ ["3", "rpi3", "arduino", "pi 3"], ["4", "rpi4", "farmduino_k16", "pi 4"], + ["4", "rpi4", "farmduino_k17", "pi 4"], ["01", "rpi", "express_k10", "zero w"], ["02", "rpi3", "express_k11", "zero 2 w"], + ["02", "rpi3", "express_k12", "zero 2 w"], ]; describe("", () => { diff --git a/frontend/settings/fbos_settings/fbos_details.tsx b/frontend/settings/fbos_settings/fbos_details.tsx index d4d5e942aa..5db999003d 100644 --- a/frontend/settings/fbos_settings/fbos_details.tsx +++ b/frontend/settings/fbos_settings/fbos_details.tsx @@ -10,7 +10,7 @@ import { LastSeen } from "./last_seen_row"; import moment from "moment"; import { formatTime } from "../../util"; import { - boardType, FIRMWARE_CHOICES_DDI, validFirmwareHardware, + boardType, FIRMWARE_CHOICES_DDI, hasZero2, validFirmwareHardware, } from "../firmware/firmware_hardware_support"; import { ExternalUrl, FarmBotRepo } from "../../external_urls"; import { DeviceAccountSettings } from "farmbot/dist/resources/api_resources"; @@ -63,7 +63,7 @@ export const PiDisplay = ({ chip, firmware }: PiDisplayProps): JSX.Element => { const pi = () => { switch (chip) { case "rpi": return "Zero W"; - case "rpi3": return firmware == "express_k11" ? "Zero 2 W" : "3"; + case "rpi3": return hasZero2(firmware) ? "Zero 2 W" : "3"; case "rpi4": return "4"; default: return t("Unknown"); } diff --git a/frontend/settings/fbos_settings/rpi_model.tsx b/frontend/settings/fbos_settings/rpi_model.tsx index 1ee773875b..c308cc6ac6 100644 --- a/frontend/settings/fbos_settings/rpi_model.tsx +++ b/frontend/settings/fbos_settings/rpi_model.tsx @@ -8,6 +8,7 @@ import { FirmwareHardware, TaggedDevice } from "farmbot"; import { BotState } from "../../devices/interfaces"; import { StatusIcon } from "../firmware/firmware_hardware_status"; import { Position } from "@blueprintjs/core"; +import { hasZero2 } from "../firmware/firmware_hardware_support"; export const RPI_OPTIONS: Record = { "3": { label: "Raspberry Pi 3", value: "3" }, @@ -16,13 +17,14 @@ export const RPI_OPTIONS: Record = { "02": { label: "Raspberry Pi Zero 2 W", value: "02" }, }; -const TARGETS = (firmwareHardware: string): { [x: string]: string } => ({ - "rpi3": firmwareHardware == "express_k11" - ? "Raspberry Pi Zero 2 W" - : "Raspberry Pi 3", - "rpi4": "Raspberry Pi 4", - "rpi": "Raspberry Pi Zero W", -}); +const TARGETS = + (firmwareHardware: FirmwareHardware | undefined): { [x: string]: string } => ({ + "rpi3": hasZero2(firmwareHardware) + ? "Raspberry Pi Zero 2 W" + : "Raspberry Pi 3", + "rpi4": "Raspberry Pi 4", + "rpi": "Raspberry Pi Zero W", + }); export interface RpiModelProps { dispatch: Function; @@ -89,7 +91,6 @@ export const StatusDetails = (props: StatusDetailsProps) => {

    {selection ? RPI_OPTIONS[selection].label : t("none")}

    -

    {TARGETS("" + firmwareHardware)["" + target] - || t("unknown")}

    +

    {TARGETS(firmwareHardware)["" + target] || t("unknown")}

    ; }; diff --git a/frontend/settings/firmware/__tests__/board_type_test.tsx b/frontend/settings/firmware/__tests__/board_type_test.tsx index c06d40b380..477349fd8c 100644 --- a/frontend/settings/firmware/__tests__/board_type_test.tsx +++ b/frontend/settings/firmware/__tests__/board_type_test.tsx @@ -3,6 +3,11 @@ jest.mock("../../../api/crud", () => ({ save: jest.fn(), })); +let mockFeatureBoolean = false; +jest.mock("../../../devices/should_display", () => ({ + shouldDisplayFeature: () => mockFeatureBoolean, +})); + import React from "react"; import { mount, shallow } from "enzyme"; import { BoardType } from "../board_type"; @@ -78,6 +83,7 @@ describe("", () => { }); it("displays boards", () => { + mockFeatureBoolean = false; const wrapper = mount(); const { list } = wrapper.find("FBSelect").props(); expect(list).toEqual([ @@ -90,5 +96,13 @@ describe("", () => { { label: "Farmduino (Express v1.1)", value: "express_k11" }, { label: "None", value: "none" }, ]); + expect(list?.length).toEqual(8); + }); + + it("displays more boards", () => { + mockFeatureBoolean = true; + const wrapper = mount(); + const { list } = wrapper.find("FBSelect").props(); + expect(list?.length).toEqual(10); }); }); diff --git a/frontend/settings/firmware/__tests__/firmware_hardware_support_test.ts b/frontend/settings/firmware/__tests__/firmware_hardware_support_test.ts index 638237dae3..e930618019 100644 --- a/frontend/settings/firmware/__tests__/firmware_hardware_support_test.ts +++ b/frontend/settings/firmware/__tests__/firmware_hardware_support_test.ts @@ -20,6 +20,10 @@ describe("boardType()", () => { expect(boardType("5.0.3.I")).toEqual("farmduino_k16"); }); + it("returns Farmduino k1.7", () => { + expect(boardType("5.0.3.J")).toEqual("farmduino_k17"); + }); + it("returns Farmduino Express k1.0", () => { expect(boardType("5.0.3.E")).toEqual("express_k10"); }); @@ -28,6 +32,10 @@ describe("boardType()", () => { expect(boardType("5.0.3.D")).toEqual("express_k11"); }); + it("returns Farmduino Express k1.2", () => { + expect(boardType("5.0.3.C")).toEqual("express_k12"); + }); + it("returns Arduino/RAMPS", () => { expect(boardType("5.0.3.R")).toEqual("arduino"); }); diff --git a/frontend/settings/firmware/firmware_hardware_support.ts b/frontend/settings/firmware/firmware_hardware_support.ts index 16cc73fe69..aea0d40a1d 100644 --- a/frontend/settings/firmware/firmware_hardware_support.ts +++ b/frontend/settings/firmware/firmware_hardware_support.ts @@ -1,10 +1,12 @@ import { FirmwareHardware, TaggedFbosConfig } from "farmbot"; +import { shouldDisplayFeature } from "../../devices/should_display"; +import { Feature } from "../../devices/interfaces"; export const isFwHardwareValue = (x?: unknown): x is FirmwareHardware => { const values: FirmwareHardware[] = [ "arduino", - "farmduino", "farmduino_k14", "farmduino_k15", "farmduino_k16", - "express_k10", "express_k11", + "farmduino", "farmduino_k14", "farmduino_k15", "farmduino_k16", "farmduino_k17", + "express_k10", "express_k11", "express_k12", "none", ]; return !!values.includes(x as FirmwareHardware); @@ -13,11 +15,13 @@ export const isFwHardwareValue = (x?: unknown): x is FirmwareHardware => { const ordered: FirmwareHardware[] = [ "express_k10", "express_k11", + "express_k12", "arduino", "farmduino", "farmduino_k14", "farmduino_k15", "farmduino_k16", + "farmduino_k17", "none", ]; @@ -35,13 +39,17 @@ export const getFwHardwareValue = }; const NO_BUTTONS = ["arduino", "farmduino", "none"]; -const EXPRESS_BOARDS = ["express_k10", "express_k11"]; +const EXPRESS_BOARDS = ["express_k10", "express_k11", "express_k12"]; const NO_SENSORS = [...EXPRESS_BOARDS]; const NO_ENCODERS = [...EXPRESS_BOARDS]; const NO_TOOLS = [...EXPRESS_BOARDS]; const NO_ETHERNET = ["express_k10"]; +const NO_ZERO_2 = ["express_k10"]; const NO_EXTRA_BUTTONS = [...EXPRESS_BOARDS]; const NO_TMC = ["arduino", "farmduino", "farmduino_k14"]; +const HAS_WEEDER = [ + "arduino", "farmduino", "farmduino_k14", "farmduino_k15", "farmduino_k16", +]; const NO_ROTARY = ["arduino", "farmduino", "farmduino_k14", "farmduino_k15"] .concat(EXPRESS_BOARDS); @@ -66,12 +74,19 @@ export const hasSensors = (firmwareHardware: FirmwareHardware | undefined) => export const hasUTM = (firmwareHardware: FirmwareHardware | undefined) => !firmwareHardware || !NO_TOOLS.includes(firmwareHardware); +export const hasWeeder = (firmwareHardware: FirmwareHardware | undefined) => + !firmwareHardware || HAS_WEEDER.includes(firmwareHardware); + export const hasRotaryTool = (firmwareHardware: FirmwareHardware | undefined) => !firmwareHardware || !NO_ROTARY.includes(firmwareHardware); export const hasEthernet = (firmwareHardware: FirmwareHardware | undefined) => !firmwareHardware || !NO_ETHERNET.includes(firmwareHardware); +export const hasZero2 = (firmwareHardware: FirmwareHardware | undefined) => + isExpress(firmwareHardware) + && !NO_ZERO_2.includes(firmwareHardware as FirmwareHardware); + const getBoardIdentifier = (firmwareVersion: string | undefined): string => firmwareVersion ? firmwareVersion.split(".")[3] : "undefined"; @@ -112,8 +127,10 @@ const FIRMWARE_LOOKUP: { [id: string]: FirmwareHardware } = { G: "farmduino_k14", H: "farmduino_k15", I: "farmduino_k16", + J: "farmduino_k17", E: "express_k10", D: "express_k11", + C: "express_k12", }; enum BoardLabels { @@ -122,8 +139,10 @@ enum BoardLabels { farmduino_k14 = "Farmduino (Genesis v1.4)", farmduino_k15 = "Farmduino (Genesis v1.5)", farmduino_k16 = "Farmduino (Genesis v1.6)", + farmduino_k17 = "Farmduino (Genesis v1.7)", express_k10 = "Farmduino (Express v1.0)", express_k11 = "Farmduino (Express v1.1)", + express_k12 = "Farmduino (Express v1.2)", none = "None", } @@ -133,8 +152,10 @@ enum KitLabels { farmduino_k14 = "Genesis v1.4", farmduino_k15 = "Genesis v1.5", farmduino_k16 = "Genesis v1.6", + farmduino_k17 = "Genesis v1.7", express_k10 = "Express v1.0", express_k11 = "Express v1.1", + express_k12 = "Express v1.2", none = "None", unknown = "Farmduino", } @@ -145,8 +166,10 @@ const KIT_LOOKUP = { farmduino_k14: KitLabels.farmduino_k14, farmduino_k15: KitLabels.farmduino_k15, farmduino_k16: KitLabels.farmduino_k16, + farmduino_k17: KitLabels.farmduino_k17, express_k10: KitLabels.express_k10, express_k11: KitLabels.express_k11, + express_k12: KitLabels.express_k12, none: KitLabels.none, unknown: KitLabels.unknown, }; @@ -156,8 +179,10 @@ const FARMDUINO = { label: BoardLabels.farmduino, value: "farmduino" }; 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 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 = { @@ -166,8 +191,10 @@ export const FIRMWARE_CHOICES_DDI = { [FARMDUINO_K14.value]: FARMDUINO_K14, [FARMDUINO_K15.value]: FARMDUINO_K15, [FARMDUINO_K16.value]: FARMDUINO_K16, + [FARMDUINO_K17.value]: FARMDUINO_K17, [EXPRESS_K10.value]: EXPRESS_K10, [EXPRESS_K11.value]: EXPRESS_K11, + [EXPRESS_K12.value]: EXPRESS_K12, [NONE.value]: NONE, }; @@ -177,7 +204,9 @@ export const getFirmwareChoices = () => ([ FARMDUINO_K14, FARMDUINO_K15, FARMDUINO_K16, + ...(shouldDisplayFeature(Feature.farmduino_k17) ? [FARMDUINO_K17] : []), EXPRESS_K10, EXPRESS_K11, + ...(shouldDisplayFeature(Feature.express_k12) ? [EXPRESS_K12] : []), NONE, ]); diff --git a/frontend/settings/firmware/firmware_path.tsx b/frontend/settings/firmware/firmware_path.tsx index 9e79e9ac3b..d74428518f 100644 --- a/frontend/settings/firmware/firmware_path.tsx +++ b/frontend/settings/firmware/firmware_path.tsx @@ -18,7 +18,7 @@ export const ChangeFirmwarePath = (props: ChangeFirmwarePathProps) => { label: t("ttyAMA0 (recommended for Express v1.0)"), value: "ttyAMA0", }, ttyUSB0: { - label: t("ttyUSB0 (recommended for Express v1.1)"), value: "ttyUSB0", + label: t("ttyUSB0 (recommended for Express v1.1+)"), value: "ttyUSB0", }, manual: { label: t("Manual input"), value: "manual" }, }; diff --git a/frontend/settings/hardware_settings/default_values.ts b/frontend/settings/hardware_settings/default_values.ts index 1cb7a42ea5..0b1532312f 100644 --- a/frontend/settings/hardware_settings/default_values.ts +++ b/frontend/settings/hardware_settings/default_values.ts @@ -155,9 +155,11 @@ export const getDefaultFwConfigValue = case "farmduino_k14": case "farmduino_k15": case "farmduino_k16": + case "farmduino_k17": return DEFAULT_GENESIS_FIRMWARE_CONFIG_VALUES[key]; case "express_k10": case "express_k11": + case "express_k12": return DEFAULT_EXPRESS_FIRMWARE_CONFIG_VALUES[key]; default: return DEFAULT_FIRMWARE_CONFIG_VALUES[key]; diff --git a/frontend/tools/__tests__/add_tool_test.tsx b/frontend/tools/__tests__/add_tool_test.tsx index 8cc32dd9d3..8195a9f364 100644 --- a/frontend/tools/__tests__/add_tool_test.tsx +++ b/frontend/tools/__tests__/add_tool_test.tsx @@ -107,8 +107,10 @@ describe("", () => { ["farmduino_k14", 6], ["farmduino_k15", 8], ["farmduino_k16", 9], + ["farmduino_k17", 9], ["express_k10", 3], ["express_k11", 3], + ["express_k12", 3], ])("adds peripherals: %s", (firmware, expectedAdds) => { const p = fakeProps(); p.firmwareHardware = firmware; diff --git a/frontend/tools/add_tool.tsx b/frontend/tools/add_tool.tsx index 88fdd1a62c..7e7296336e 100644 --- a/frontend/tools/add_tool.tsx +++ b/frontend/tools/add_tool.tsx @@ -103,6 +103,7 @@ export class RawAddTool extends React.Component { ...TROUGHS, ]; case "farmduino_k16": + case "farmduino_k17": return [ ...BASE_TOOLS, t("Rotary Tool"), @@ -111,6 +112,7 @@ export class RawAddTool extends React.Component { ]; case "express_k10": case "express_k11": + case "express_k12": return [ ...BASE_TOOLS, ...TROUGHS, diff --git a/frontend/util/version.ts b/frontend/util/version.ts index a6095fc264..78e2ad166b 100644 --- a/frontend/util/version.ts +++ b/frontend/util/version.ts @@ -76,6 +76,7 @@ export function semverCompare(left: string, right: string): SemverResult { * for shouldDisplay() */ export enum MinVersionOverride { + DEMO = "99.99.99", NEVER = "999.999.999", } @@ -84,6 +85,8 @@ export enum FbosVersionFallback { } const fallbackData: MinOsFeatureLookup = { + [Feature.express_k12]: MinVersionOverride.NEVER, // available: "15.4.6", + [Feature.farmduino_k17]: MinVersionOverride.DEMO, // available: "15.4.6", [Feature.planted_at_now]: MinVersionOverride.NEVER, }; diff --git a/frontend/wizard/checks.tsx b/frontend/wizard/checks.tsx index 8a10e5aa8b..8e33747446 100644 --- a/frontend/wizard/checks.tsx +++ b/frontend/wizard/checks.tsx @@ -333,13 +333,17 @@ const SEED_DATA_OPTION_TO_FW_HARDWARE: Record = { "genesis_1.4": "farmduino_k14", "genesis_1.5": "farmduino_k15", "genesis_1.6": "farmduino_k16", + "genesis_1.7": "farmduino_k17", "genesis_xl_1.4": "farmduino_k14", "genesis_xl_1.5": "farmduino_k15", "genesis_xl_1.6": "farmduino_k16", + "genesis_xl_1.7": "farmduino_k17", "express_1.0": "express_k10", "express_1.1": "express_k11", + "express_1.2": "express_k12", "express_xl_1.0": "express_k10", "express_xl_1.1": "express_k11", + "express_xl_1.2": "express_k12", "none": "none", }; @@ -349,8 +353,10 @@ const FW_HARDWARE_TO_RPI: Record = { "farmduino_k14": "3", "farmduino_k15": "3", "farmduino_k16": undefined, + "farmduino_k17": "4", "express_k10": "01", "express_k11": "02", + "express_k12": "02", "none": "3", }; diff --git a/frontend/wizard/data.ts b/frontend/wizard/data.ts index bfa7e09139..7396aff850 100644 --- a/frontend/wizard/data.ts +++ b/frontend/wizard/data.ts @@ -46,7 +46,7 @@ import { } from "./checks"; import { TaggedWizardStepResult } from "farmbot"; import { - hasEthernet, hasExtraButtons, hasRotaryTool, hasUTM, isExpress, + hasEthernet, hasExtraButtons, hasRotaryTool, hasUTM, hasWeeder, isExpress, } from "../settings/firmware/firmware_hardware_support"; import { BooleanSetting } from "../session_keys"; import { ExternalUrl } from "../external_urls"; @@ -1397,7 +1397,7 @@ export const WIZARD_STEPS = (props: WizardStepDataProps): WizardSteps => { outcomes: [ ], }, - ...(hasUTM(firmwareHardware) + ...(hasWeeder(firmwareHardware) ? [{ section: WizardSectionSlug.tools, slug: WizardStepSlug.weeder, diff --git a/lib/tasks/releases.rake b/lib/tasks/releases.rake index 4dddfd4678..f23d115d58 100644 --- a/lib/tasks/releases.rake +++ b/lib/tasks/releases.rake @@ -54,6 +54,17 @@ namespace :releases do release end + def self.get_brief_release_info + info = [] + Release.all.map do |r| + if r.platform == "rpi" + info.push("#{r.channel.ljust(8)} #{r.version.ljust(14)}" + + "#{r.created_at.to_s.slice(0, 10)}") + end + end + info.join("\n") + end + def self.print_all_existing_releases puts "" Release.all.map do |r| @@ -61,12 +72,7 @@ namespace :releases do "#{r.platform.ljust(6)} #{r.version.ljust(14)} #{r.created_at}" end puts "" - Release.all.map do |r| - if r.platform == "rpi" - puts "#{r.channel.ljust(8)} #{r.version.ljust(14)}" + - "#{r.created_at.to_s.slice(0, 10)}" - end - end + puts get_brief_release_info puts "" end @@ -93,6 +99,31 @@ namespace :releases do exit 1 end end + + def self.post_summary + webhook_url = ENV["RELEASE_WEBHOOK_URL"] + if webhook_url + server = Release.first.image_url.split("/")[3].split("-")[1] + title = "current releases: #{server}" + info = title + "\n```#{get_brief_release_info}```" + payload = { + "mrkdwn": true, + "text": title, + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": info, + } + } + ], + }.to_json + Faraday.post(webhook_url, + payload, + "Content-Type" => "application/json") + end + end end desc "Send upgrade notification to devices that are online" @@ -118,5 +149,6 @@ namespace :releases do release.destroy! end ReleaseTask.print_all_existing_releases + ReleaseTask.post_summary end end diff --git a/package.json b/package.json index 4a0de367dd..d13c9b5dda 100644 --- a/package.json +++ b/package.json @@ -31,16 +31,16 @@ "author": "farmbot.io", "license": "MIT", "dependencies": { - "@blueprintjs/core": "5.5.1", - "@blueprintjs/select": "5.0.16", + "@blueprintjs/core": "5.6.0", + "@blueprintjs/select": "5.0.17", "@monaco-editor/react": "4.6.0", - "@parcel/transformer-sass": "2.10.1", - "@parcel/transformer-typescript-tsc": "2.10.1", + "@parcel/transformer-sass": "2.10.2", + "@parcel/transformer-typescript-tsc": "2.10.2", "@types/lodash": "4.14.200", "@types/markdown-it": "13.0.5", - "@types/node": "20.8.9", + "@types/node": "20.8.10", "@types/promise-timeout": "1.3.2", - "@types/react": "18.2.33", + "@types/react": "18.2.34", "@types/react-color": "3.0.9", "@types/react-dom": "18.2.14", "@types/ws": "8.5.8", @@ -48,19 +48,19 @@ "bowser": "2.11.0", "browser-speech": "1.1.1", "events": "3.3.0", - "farmbot": "15.8.1", + "farmbot": "15.8.5", "i18next": "23.6.0", "lodash": "4.17.21", "markdown-it": "13.0.2", "markdown-it-emoji": "2.0.2", "moment": "2.29.4", "monaco-editor": "0.44.0", - "mqtt": "5.1.3", - "npm": "10.2.1", - "parcel": "2.10.1", + "mqtt": "5.1.4", + "npm": "10.2.3", + "parcel": "2.10.2", "process": "0.11.10", "promise-timeout": "1.3.0", - "punycode": "2.3.0", + "punycode": "2.3.1", "querystring-es3": "0.2.1", "react": "18.2.0", "react-color": "2.19.3", @@ -76,14 +76,14 @@ }, "devDependencies": { "@types/enzyme": "3.10.12", - "@types/jest": "29.5.6", + "@types/jest": "29.5.7", "@types/readable-stream": "4.0.4", - "@typescript-eslint/eslint-plugin": "6.9.0", - "@typescript-eslint/parser": "6.9.0", + "@typescript-eslint/eslint-plugin": "6.9.1", + "@typescript-eslint/parser": "6.9.1", "@wojtekmaj/enzyme-adapter-react-17": "0.8.0", "coveralls": "3.1.1", "enzyme": "3.11.0", - "eslint": "8.52.0", + "eslint": "8.53.0", "eslint-plugin-eslint-comments": "3.2.0", "eslint-plugin-import": "2.29.0", "eslint-plugin-jest": "27.6.0", diff --git a/spec/controllers/api/ai/ai_controller_spec.rb b/spec/controllers/api/ai/ai_controller_spec.rb index 1a95ec6c62..43f98cc342 100644 --- a/spec/controllers/api/ai/ai_controller_spec.rb +++ b/spec/controllers/api/ai/ai_controller_spec.rb @@ -8,7 +8,7 @@ def chunk(content, done=nil) "data: {\"id\":\"id\",\"object\":\"chat.completion.chunk\"," \ "\"created\":12345,\"model\":\"gpt-4\",\"choices\":[{\"delta\":{" \ - "\"content\":\"#{content}\"},\"index\":0,\"finish_reason\":#{done.to_json}}]}" + "\"content\":\"#{content}\"},\"index\":0,\"finish_reason\":#{done.to_json}}]}\n\n" end URL_PATTERN = /raw\.githubusercontent\.com/ diff --git a/spec/controllers/api/demo_accounts/demo_account_controller_spec.rb b/spec/controllers/api/demo_accounts/demo_account_controller_spec.rb index acc52e2197..ee7591c09c 100644 --- a/spec/controllers/api/demo_accounts/demo_account_controller_spec.rb +++ b/spec/controllers/api/demo_accounts/demo_account_controller_spec.rb @@ -7,7 +7,7 @@ it "creates a guest account", :slow do Transport.current.clear! secret = SecureRandom.alphanumeric.downcase - p = { secret: secret } + p = { secret: secret, product_line: "genesis_1.7" } run_jobs_now { post :create, body: p.to_json } user, secret_again = Transport .current diff --git a/spec/controllers/api/devices/devices_controller_seed_spec.rb b/spec/controllers/api/devices/devices_controller_seed_spec.rb index 8a9c027815..22b32b30a2 100644 --- a/spec/controllers/api/devices/devices_controller_seed_spec.rb +++ b/spec/controllers/api/devices/devices_controller_seed_spec.rb @@ -268,7 +268,7 @@ def settings_default_map_size_y?(device) expect(device.reload.name).to eq(old_name) end - def start_tests(product_line, publish = true) + def start_tests(product_line, publish = true, demo = false) u = FactoryBot.create(:user) ClimateControl.modify AUTHORIZED_PUBLISHER: u.email do if publish @@ -295,7 +295,7 @@ def start_tests(product_line, publish = true) end sign_in user run_jobs_now do - post :seed, body: { product_line: product_line }.to_json + post :seed, body: { product_line: product_line, demo: demo }.to_json end expect(response.status).to eq(200) device.reload @@ -680,6 +680,81 @@ def check_slot_pairing(slot, expected_name) expect(settings_default_map_size_y?(device)).to eq(1400) end + it "seeds accounts with Genesis 1.7 data" do + start_tests "genesis_1.7" + + 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(plants?(device)).to be false + 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(-200) + 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(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(1400) + end + it "seeds accounts with Genesis XL 1.4 data" do start_tests "genesis_xl_1.4" @@ -828,6 +903,81 @@ def check_slot_pairing(slot, expected_name) expect(settings_default_map_size_y?(device)).to eq(2900) end + it "seeds accounts with Genesis XL 1.7 data" do + start_tests "genesis_xl_1.7" + + 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(plants?(device)).to be false + 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(-200) + 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(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(2900) + end + it "seeds accounts with Genesis XL 1.6 data" do start_tests "genesis_xl_1.6" @@ -1042,6 +1192,75 @@ def check_slot_pairing(slot, expected_name) expect(settings_default_map_size_y?(device)).to eq(1200) end + it "seeds accounts with Express 1.2 data" do + start_tests "express_1.2" + + expect(peripherals_lighting?(device).pin).to eq(7) + expect(peripherals_peripheral_4?(device)).to_not be + expect(peripherals_peripheral_5?(device)).to_not be + expect(peripherals_vacuum?(device).pin).to be(9) + expect(peripherals_water?(device).pin).to be(8) + expect(peripherals_rotary_tool?(device)).to_not be + expect(peripherals_rotary_tool_reverse?(device)).to_not be + 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(plants?(device)).to be false + expect(sensors_soil_sensor?(device)).to_not be + expect(sensors_tool_verification?(device)).to_not be + expect(settings_device_name?(device)).to eq(Names::EXPRESS) + expect(settings_change_firmware_config_defaults?(device)).to be(false) + expect(settings_soil_height?(device)).to eq(-200) + 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(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 + expect(tool_slots_slot_4?(device)).to_not be + expect(tool_slots_slot_5?(device)).to_not be + expect(tool_slots_slot_6?(device)).to_not be + expect(tool_slots_slot_7?(device)).to_not be + expect(tool_slots_slot_8?(device)).to_not be + expect(tool_slots_slot_9?(device)).to_not be + + check_slot_pairing(tool_slots_slot_1?(device), "Seed Trough 1") + check_slot_pairing(tool_slots_slot_2?(device), "Seed Trough 2") + + expect(tools_seed_bin?(device)).to_not be + expect(tools_seed_tray?(device)).to_not be + expect(tools_seed_trough_1?(device)).to be + expect(tools_seed_trough_2?(device)).to be + expect(tools_seeder?(device)).to_not be + expect(tools_soil_sensor?(device)).to_not be + expect(tools_watering_nozzle?(device)).to be_kind_of(Tool) + expect(tools_weeder?(device)).to_not be + expect(tools_rotary?(device)).to_not be + 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_not be + 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(2900) + expect(settings_default_map_size_y?(device)).to eq(1200) + end + it "seeds accounts with Express XL 1.0 data" do start_tests "express_xl_1.0" @@ -1180,8 +1399,77 @@ def check_slot_pairing(slot, expected_name) expect(settings_default_map_size_y?(device)).to eq(2400) end + it "seeds accounts with Express XL 1.2 data" do + start_tests "express_xl_1.2" + + expect(peripherals_lighting?(device).pin).to eq(7) + expect(peripherals_peripheral_4?(device)).to_not be + expect(peripherals_peripheral_5?(device)).to_not be + expect(peripherals_vacuum?(device).pin).to be(9) + expect(peripherals_water?(device).pin).to be(8) + expect(peripherals_rotary_tool?(device)).to_not be + expect(peripherals_rotary_tool_reverse?(device)).to_not be + 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(plants?(device)).to be false + expect(sensors_soil_sensor?(device)).to_not be + expect(sensors_tool_verification?(device)).to_not be + expect(settings_device_name?(device)).to eq(Names::EXPRESS_XL) + expect(settings_change_firmware_config_defaults?(device)).to be(false) + expect(settings_soil_height?(device)).to eq(-200) + 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(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 + expect(tool_slots_slot_4?(device)).to_not be + expect(tool_slots_slot_5?(device)).to_not be + expect(tool_slots_slot_6?(device)).to_not be + expect(tool_slots_slot_7?(device)).to_not be + expect(tool_slots_slot_8?(device)).to_not be + expect(tool_slots_slot_9?(device)).to_not be + + check_slot_pairing(tool_slots_slot_1?(device), "Seed Trough 1") + check_slot_pairing(tool_slots_slot_2?(device), "Seed Trough 2") + + expect(tools_seed_bin?(device)).to_not be + expect(tools_seed_tray?(device)).to_not be + expect(tools_seed_trough_1?(device)).to be + expect(tools_seed_trough_2?(device)).to be + expect(tools_seeder?(device)).to_not be + expect(tools_soil_sensor?(device)).to_not be + expect(tools_watering_nozzle?(device)).to be_kind_of(Tool) + expect(tools_weeder?(device)).to_not be + expect(tools_rotary?(device)).to_not be + 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_not be + 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) + end + it "seeds accounts with demo account data" do - start_tests "demo_account" + start_tests "genesis_1.7", true, true expect(plants?(device)).to be true expect(point_groups_spinach?(device)).to be_kind_of(PointGroup) @@ -1190,7 +1478,7 @@ def check_slot_pairing(slot, expected_name) end it "seeds accounts when sequence versions not available: demo account" do - start_tests "demo_account", false + start_tests "genesis_1.7", false, true expect(sequences_grid?(device)).to be_kind_of(Sequence) end @@ -1207,6 +1495,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.7" do + start_tests "genesis_xl_1.7", false + + expect(sequences_mow_all_weeds?(device)).to be_kind_of(Sequence) + end + + it "seeds accounts when sequence versions not available: Genesis 1.7" do + start_tests "genesis_1.7", false + + expect(sequences_mow_all_weeds?(device)).to be_kind_of(Sequence) + end + it "does not seed accounts" do start_tests "none" diff --git a/spec/mutations/sequences/publish_spec.rb b/spec/mutations/sequences/publish_spec.rb index 1612fcf228..8b69a8feea 100644 --- a/spec/mutations/sequences/publish_spec.rb +++ b/spec/mutations/sequences/publish_spec.rb @@ -138,7 +138,7 @@ it "does not allow guests to publish" do run_jobs_now do - Users::CreateDemo.run!(secret: SecureRandom.hex) + Users::CreateDemo.run!(secret: SecureRandom.hex, product_line: "genesis_1.7") end guest = User.find_by!("email LIKE '%@farmbot.guest'") device = guest.device