diff --git a/app/mutations/devices/seeders/abstract_express.rb b/app/mutations/devices/seeders/abstract_express.rb index 0ab4feb85..557d04ccb 100644 --- a/app/mutations/devices/seeders/abstract_express.rb +++ b/app/mutations/devices/seeders/abstract_express.rb @@ -27,7 +27,7 @@ module Devices add_tool_slot(name: ToolNames::SEED_TROUGH_1, x: 0, y: 25, - z: -200, + z: 0, tool: tools_seed_trough_1, pullout_direction: ToolSlot::NONE, gantry_mounted: true) @@ -37,25 +37,18 @@ module Devices add_tool_slot(name: ToolNames::SEED_TROUGH_2, x: 0, y: 50, - z: -200, + z: 0, tool: tools_seed_trough_2, pullout_direction: ToolSlot::NONE, gantry_mounted: true) end - def tool_slots_slot_3 - add_tool_slot(name: ToolNames::SEED_TROUGH_3, - x: 0, - y: 75, - z: -200, - tool: tools_seed_trough_3, - pullout_direction: ToolSlot::NONE, - gantry_mounted: true) - end - + def tool_slots_slot_3; end def tool_slots_slot_4; end def tool_slots_slot_5; end def tool_slots_slot_6; end + def tool_slots_slot_7; end + def tool_slots_slot_8; end def tools_seed_bin; end def tools_seed_tray; end @@ -69,11 +62,6 @@ module Devices add_tool(ToolNames::SEED_TROUGH_2) end - def tools_seed_trough_3 - @tools_seed_trough_3 ||= - add_tool(ToolNames::SEED_TROUGH_3) - end - def tools_seeder; end def tools_soil_sensor; end def tools_watering_nozzle; end diff --git a/app/mutations/devices/seeders/abstract_genesis.rb b/app/mutations/devices/seeders/abstract_genesis.rb index b52665768..36c48398c 100644 --- a/app/mutations/devices/seeders/abstract_genesis.rb +++ b/app/mutations/devices/seeders/abstract_genesis.rb @@ -75,6 +75,9 @@ module Devices tool: tools_weeder) end + def tool_slots_slot_7; end + def tool_slots_slot_8; end + def tools_seed_bin @tools_seed_bin ||= add_tool(ToolNames::SEED_BIN) @@ -87,7 +90,6 @@ module Devices def tools_seed_trough_1; end def tools_seed_trough_2; end - def tools_seed_trough_3; end def tools_seeder @tools_seeder ||= diff --git a/app/mutations/devices/seeders/abstract_seeder.rb b/app/mutations/devices/seeders/abstract_seeder.rb index fd6bc4ba1..7d5a41003 100644 --- a/app/mutations/devices/seeders/abstract_seeder.rb +++ b/app/mutations/devices/seeders/abstract_seeder.rb @@ -37,7 +37,6 @@ module Devices :tools_seed_tray, :tools_seed_trough_1, :tools_seed_trough_2, - :tools_seed_trough_3, :tools_seeder, :tools_soil_sensor, :tools_watering_nozzle, @@ -50,6 +49,8 @@ module Devices :tool_slots_slot_4, :tool_slots_slot_5, :tool_slots_slot_6, + :tool_slots_slot_7, + :tool_slots_slot_8, # WEBCAM FEEDS =========================== :webcam_feeds, @@ -152,11 +153,12 @@ module Devices def tool_slots_slot_4; end def tool_slots_slot_5; end def tool_slots_slot_6; end + def tool_slots_slot_7; end + def tool_slots_slot_8; end def tools_seed_bin; end def tools_seed_tray; end def tools_seed_trough_1; end def tools_seed_trough_2; end - def tools_seed_trough_3; end def tools_seeder; end def tools_soil_sensor; end def tools_watering_nozzle; end diff --git a/app/mutations/devices/seeders/constants.rb b/app/mutations/devices/seeders/constants.rb index c6f56b654..4962dd36c 100644 --- a/app/mutations/devices/seeders/constants.rb +++ b/app/mutations/devices/seeders/constants.rb @@ -31,7 +31,6 @@ module Devices LIGHTING = "Lighting" SEED_TROUGH_1 = "Seed Trough 1" SEED_TROUGH_2 = "Seed Trough 2" - SEED_TROUGH_3 = "Seed Trough 3" end # Stub plants ============================== diff --git a/app/mutations/devices/seeders/genesis_one_five.rb b/app/mutations/devices/seeders/genesis_one_five.rb index dde23e30d..2731dd21a 100644 --- a/app/mutations/devices/seeders/genesis_one_five.rb +++ b/app/mutations/devices/seeders/genesis_one_five.rb @@ -6,6 +6,36 @@ module Devices .fbos_config .update!(firmware_hardware: FbosConfig::FARMDUINO_K15) end + + def tool_slots_slot_7 + add_tool_slot(name: ToolNames::SEED_TROUGH_1, + x: 0, + y: 25, + z: 0, + tool: tools_seed_trough_1, + pullout_direction: ToolSlot::NONE, + gantry_mounted: true) + end + + def tool_slots_slot_8 + add_tool_slot(name: ToolNames::SEED_TROUGH_2, + x: 0, + y: 50, + z: 0, + tool: tools_seed_trough_2, + pullout_direction: ToolSlot::NONE, + gantry_mounted: true) + end + + def tools_seed_trough_1 + @tools_seed_trough_1 ||= + add_tool(ToolNames::SEED_TROUGH_1) + end + + def tools_seed_trough_2 + @tools_seed_trough_2 ||= + add_tool(ToolNames::SEED_TROUGH_2) + end end end end diff --git a/app/mutations/devices/seeders/genesis_xl_one_five.rb b/app/mutations/devices/seeders/genesis_xl_one_five.rb index b3e291a1f..7333947ae 100644 --- a/app/mutations/devices/seeders/genesis_xl_one_five.rb +++ b/app/mutations/devices/seeders/genesis_xl_one_five.rb @@ -18,6 +18,36 @@ module Devices def settings_default_map_size_y device.web_app_config.update!(map_size_y: 2_900) end + + def tool_slots_slot_7 + add_tool_slot(name: ToolNames::SEED_TROUGH_1, + x: 0, + y: 25, + z: 0, + tool: tools_seed_trough_1, + pullout_direction: ToolSlot::NONE, + gantry_mounted: true) + end + + def tool_slots_slot_8 + add_tool_slot(name: ToolNames::SEED_TROUGH_2, + x: 0, + y: 50, + z: 0, + tool: tools_seed_trough_2, + pullout_direction: ToolSlot::NONE, + gantry_mounted: true) + end + + def tools_seed_trough_1 + @tools_seed_trough_1 ||= + add_tool(ToolNames::SEED_TROUGH_1) + end + + def tools_seed_trough_2 + @tools_seed_trough_2 ||= + add_tool(ToolNames::SEED_TROUGH_2) + end end end end diff --git a/app/mutations/devices/seeders/none.rb b/app/mutations/devices/seeders/none.rb index ea3a49bb1..714c2050c 100644 --- a/app/mutations/devices/seeders/none.rb +++ b/app/mutations/devices/seeders/none.rb @@ -28,11 +28,12 @@ module Devices def tool_slots_slot_4; end def tool_slots_slot_5; end def tool_slots_slot_6; end + def tool_slots_slot_7; end + def tool_slots_slot_8; end def tools_seed_bin; end def tools_seed_tray; end def tools_seed_trough_1; end def tools_seed_trough_2; end - def tools_seed_trough_3; end def tools_seeder; end def tools_soil_sensor; end def tools_watering_nozzle; end diff --git a/frontend/__test_support__/control_panel_state.ts b/frontend/__test_support__/control_panel_state.ts index 13eddf61a..30fdacdeb 100644 --- a/frontend/__test_support__/control_panel_state.ts +++ b/frontend/__test_support__/control_panel_state.ts @@ -4,7 +4,10 @@ export const panelState = (): ControlPanelState => { return { homing_and_calibration: false, motors: false, - encoders_and_endstops: false, + encoders: false, + endstops: false, + error_handling: false, + pin_bindings: false, danger_zone: false, power_and_reset: false, pin_guard: false diff --git a/frontend/__test_support__/fake_state/bot.ts b/frontend/__test_support__/fake_state/bot.ts index ec6c82df5..98151048c 100644 --- a/frontend/__test_support__/fake_state/bot.ts +++ b/frontend/__test_support__/fake_state/bot.ts @@ -4,12 +4,15 @@ export const bot: Everything["bot"] = { "consistent": true, "stepSize": 100, "controlPanelState": { - "homing_and_calibration": false, - "motors": false, - "encoders_and_endstops": false, - "danger_zone": false, - "power_and_reset": false, - "pin_guard": false, + homing_and_calibration: false, + motors: false, + encoders: false, + endstops: false, + error_handling: false, + pin_bindings: false, + danger_zone: false, + power_and_reset: false, + pin_guard: false, }, "hardware": { "gpio_registry": {}, diff --git a/frontend/__tests__/app_test.tsx b/frontend/__tests__/app_test.tsx index 70e8e305f..d13158a47 100644 --- a/frontend/__tests__/app_test.tsx +++ b/frontend/__tests__/app_test.tsx @@ -157,7 +157,6 @@ describe("mapStateToProps()", () => { const state = fakeState(); const config = fakeFbosConfig(); config.body.auto_sync = true; - config.body.api_migrated = true; const fakeEnv = fakeFarmwareEnv(); state.resources = buildResourceIndex([config, fakeEnv]); state.bot.minOsFeatureData = { api_farmware_env: "8.0.0" }; diff --git a/frontend/__tests__/external_urls_test.ts b/frontend/__tests__/external_urls_test.ts new file mode 100644 index 000000000..156e1932b --- /dev/null +++ b/frontend/__tests__/external_urls_test.ts @@ -0,0 +1,33 @@ +jest.unmock("../external_urls"); +import { ExternalUrl } from "../external_urls"; + +/* tslint:disable:max-line-length */ + +describe("ExternalUrl", () => { + it("returns urls", () => { + expect(ExternalUrl.featureMinVersions) + .toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/FEATURE_MIN_VERSIONS.json"); + expect(ExternalUrl.osReleaseNotes) + .toEqual("https://raw.githubusercontent.com/FarmBot/farmbot_os/RELEASE_NOTES.md"); + expect(ExternalUrl.latestRelease) + .toEqual("https://api.github.com/repos/FarmBot/farmbot_os/releases/latest"); + expect(ExternalUrl.webAppRepo) + .toEqual("https://github.com/FarmBot/Farmbot-Web-App"); + expect(ExternalUrl.gitHubFarmBot) + .toEqual("https://github.com/FarmBot"); + expect(ExternalUrl.softwareDocs) + .toEqual("https://software.farm.bot/docs"); + expect(ExternalUrl.softwareForum) + .toEqual("http://forum.farmbot.org/c/software"); + expect(ExternalUrl.OpenFarm.cropApi) + .toEqual("https://openfarm.cc/api/v1/crops/"); + expect(ExternalUrl.OpenFarm.cropBrowse) + .toEqual("https://openfarm.cc/crops/"); + expect(ExternalUrl.OpenFarm.newCrop) + .toEqual("https://openfarm.cc/en/crops/new"); + expect(ExternalUrl.Videos.desktop) + .toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Farm_Designer_Loop.mp4?9552037556691879018"); + expect(ExternalUrl.Videos.mobile) + .toEqual("https://cdn.shopify.com/s/files/1/2040/0289/files/Controls.png?9668345515035078097"); + }); +}); diff --git a/frontend/api/api.ts b/frontend/api/api.ts index 03d5a55d0..264242e62 100644 --- a/frontend/api/api.ts +++ b/frontend/api/api.ts @@ -158,6 +158,10 @@ export class API { get farmwareInstallationPath() { return `${this.baseUrl}/api/farmware_installations/`; } + /** /api/first_party_farmwares */ + get firstPartyFarmwarePath() { + return `${this.baseUrl}/api/first_party_farmwares`; + } /** /api/alerts/:id */ get alertPath() { return `${this.baseUrl}/api/alerts/`; } /** /api/global_bulletins/:id */ diff --git a/frontend/apology.tsx b/frontend/apology.tsx index a00871358..bd2ac6328 100644 --- a/frontend/apology.tsx +++ b/frontend/apology.tsx @@ -1,5 +1,6 @@ import * as React from "react"; import { Session } from "./session"; +import { ExternalUrl } from "./external_urls"; const OUTER_STYLE: React.CSSProperties = { borderRadius: "10px", @@ -47,7 +48,7 @@ export function Apology(_: {}) {
  • Send a report to our developer team via the  - FarmBot software + FarmBot software forum. Including additional information (such as steps leading up to the error) helps us identify solutions more quickly. diff --git a/frontend/auth/actions.ts b/frontend/auth/actions.ts index 76126927c..9527939f0 100644 --- a/frontend/auth/actions.ts +++ b/frontend/auth/actions.ts @@ -1,6 +1,6 @@ import axios from "axios"; import { - fetchReleases, fetchMinOsFeatureData, FEATURE_MIN_VERSIONS_URL, + fetchReleases, fetchMinOsFeatureData, fetchLatestGHBetaRelease } from "../devices/actions"; import { AuthState } from "./interfaces"; @@ -16,6 +16,7 @@ import { Actions } from "../constants"; import { connectDevice } from "../connectivity/connect_device"; import { getFirstPartyFarmwareList } from "../farmware/actions"; import { readOnlyInterceptor } from "../read_only_mode"; +import { ExternalUrl } from "../external_urls"; export function didLogin(authState: AuthState, dispatch: Function) { API.setBaseUrl(authState.token.unencoded.iss); @@ -24,7 +25,7 @@ export function didLogin(authState: AuthState, dispatch: Function) { beta_os_update_server && beta_os_update_server != "NOT_SET" && dispatch(fetchLatestGHBetaRelease(beta_os_update_server)); dispatch(getFirstPartyFarmwareList()); - dispatch(fetchMinOsFeatureData(FEATURE_MIN_VERSIONS_URL)); + dispatch(fetchMinOsFeatureData(ExternalUrl.featureMinVersions)); dispatch(setToken(authState)); Sync.fetchSyncData(dispatch); dispatch(connectDevice(authState)); diff --git a/frontend/connectivity/__tests__/connect_device/status_checks_test.ts b/frontend/connectivity/__tests__/connect_device/status_checks_test.ts index fca02b4d8..8b0c3d78f 100644 --- a/frontend/connectivity/__tests__/connect_device/status_checks_test.ts +++ b/frontend/connectivity/__tests__/connect_device/status_checks_test.ts @@ -1,15 +1,9 @@ -jest.mock("../../slow_down", () => { - return { - slowDown: jest.fn((fn: Function) => fn), - }; -}); - -jest.mock("../../../devices/actions", () => ({ - badVersion: jest.fn(), - EXPECTED_MAJOR: 1, - EXPECTED_MINOR: 0, +jest.mock("../../slow_down", () => ({ + slowDown: jest.fn((fn: Function) => fn) })); +jest.mock("../../../devices/actions", () => ({ badVersion: jest.fn() })); + import { onStatus, incomingStatus, @@ -49,8 +43,10 @@ describe("onStatus()", () => { }); it("version ok", () => { + globalConfig.MINIMUM_FBOS_VERSION = "1.0.0"; callOnStatus("1.0.0"); expect(badVersion).not.toHaveBeenCalled(); + delete globalConfig.MINIMUM_FBOS_VERSION; }); }); diff --git a/frontend/connectivity/connect_device.ts b/frontend/connectivity/connect_device.ts index 3922c62a5..c0ce91e2a 100644 --- a/frontend/connectivity/connect_device.ts +++ b/frontend/connectivity/connect_device.ts @@ -8,13 +8,7 @@ import { success, error, info, warning, fun, busy } from "../toast/toast"; import { HardwareState } from "../devices/interfaces"; import { GetState, ReduxAction } from "../redux/interfaces"; import { Content, Actions } from "../constants"; -import { - EXPECTED_MAJOR, - EXPECTED_MINOR, - commandOK, - badVersion, - commandErr -} from "../devices/actions"; +import { commandOK, badVersion, commandErr } from "../devices/actions"; import { init } from "../api/crud"; import { AuthState } from "../auth/interfaces"; import { autoSync } from "./auto_sync"; @@ -123,7 +117,7 @@ const setBothUp = () => bothUp(); const legacyChecks = (getState: GetState) => { const { controller_version } = getState().bot.hardware.informational_settings; if (HACKY_FLAGS.needVersionCheck && controller_version) { - const IS_OK = versionOK(controller_version, EXPECTED_MAJOR, EXPECTED_MINOR); + const IS_OK = versionOK(controller_version); if (!IS_OK) { badVersion(); } HACKY_FLAGS.needVersionCheck = false; } diff --git a/frontend/constants.ts b/frontend/constants.ts index 71388071f..c76339cb7 100644 --- a/frontend/constants.ts +++ b/frontend/constants.ts @@ -39,8 +39,8 @@ export namespace ToolTips { few sequences to verify that everything works as expected.`); export const PIN_BINDINGS = - trim(`Assign a sequence to execute when a Raspberry Pi GPIO pin is - activated.`); + trim(`Assign an action or sequence to execute when a Raspberry Pi + GPIO pin is activated.`); export const PIN_BINDING_WARNING = trim(`Warning: Binding to a pin without a physical button and @@ -51,24 +51,38 @@ export namespace ToolTips { trim(`Diagnose connectivity issues with FarmBot and the browser.`); // Hardware Settings: Homing and Calibration - export const HOMING = + export const HOMING_ENCODERS = trim(`If encoders or end-stops are enabled, home axis (find zero).`); - export const CALIBRATION = + export const HOMING_STALL_DETECTION = + trim(`If stall detection or end-stops are enabled, home axis + (find zero).`); + + export const CALIBRATION_ENCODERS = trim(`If encoders or end-stops are enabled, home axis and determine maximum.`); + export const CALIBRATION_STALL_DETECTION = + trim(`If stall detection or end-stops are enabled, home axis and + determine maximum.`); + export const SET_ZERO_POSITION = trim(`Set the current location as zero.`); - export const FIND_HOME_ON_BOOT = + export const FIND_HOME_ON_BOOT_ENCODERS = trim(`If encoders or end-stops are enabled, find the home position - when the device powers on. - Warning! This will perform homing on all axes when the - device powers on. Encoders or endstops must be enabled. + when the device powers on. Warning! This will perform homing on all + axes when the device powers on. Encoders or endstops must be enabled. It is recommended to make sure homing works properly before enabling this feature. (default: disabled)`); + export const FIND_HOME_ON_BOOT_STALL_DETECTION = + trim(`If stall detection or end-stops are enabled, find the home + position when the device powers on. Warning! This will perform homing + on all axes when the device powers on. Stall detection or endstops + must be enabled. It is recommended to make sure homing works properly + before enabling this feature. (default: disabled)`); + export const STOP_AT_HOME = trim(`Stop at the home location of the axis. (default: disabled)`); @@ -85,18 +99,7 @@ export namespace ToolTips { trim(`Set the length of each axis to provide software limits. Used only if STOP AT MAX is enabled. (default: 0 (disabled))`); - export const TIMEOUT_AFTER = - trim(`Amount of time to wait for a command to execute before stopping. - (default: 120s)`); - // Hardware Settings: Motors - export const MAX_MOVEMENT_RETRIES = - trim(`Number of times to retry a movement before stopping. (default: 3)`); - - export const E_STOP_ON_MOV_ERR = - trim(`Emergency stop if movement is not complete after the maximum - number of retries. (default: disabled)`); - export const MAX_SPEED = trim(`Maximum travel speed after acceleration in millimeters per second. (default: x: 80mm/s, y: 80mm/s, z: 16mm/s)`); @@ -132,18 +135,22 @@ export namespace ToolTips { export const MOTOR_CURRENT = trim(`Motor current in milliamps. (default: 600)`); - export const STALL_SENSITIVITY = - trim(`Motor stall sensitivity. (default: 30)`); - export const ENABLE_X2_MOTOR = trim(`Enable use of a second x-axis motor. Connects to E0 on RAMPS. (default: enabled)`); - // Hardware Settings: Encoders and Endstops + // Hardware Settings: Encoders / Stall Detection export const ENABLE_ENCODERS = trim(`Enable use of rotary encoders for stall detection, calibration and homing. (default: enabled)`); + export const ENABLE_STALL_DETECTION = + trim(`Enable use of motor stall detection for detecting missed steps, + calibration and homing. (default: enabled)`); + + export const STALL_SENSITIVITY = + trim(`Motor stall sensitivity. (default: 30)`); + export const ENCODER_POSITIONING = trim(`Use encoders for positioning. (default: disabled)`); @@ -151,17 +158,22 @@ export namespace ToolTips { trim(`Reverse the direction of encoder position reading. (default: disabled)`); - export const MAX_MISSED_STEPS = + export const MAX_MISSED_STEPS_ENCODERS = trim(`Number of steps missed (determined by encoder) before motor is considered to have stalled. (default: 5)`); - export const ENCODER_MISSED_STEP_DECAY = + export const MAX_MISSED_STEPS_STALL_DETECTION = + trim(`Number of steps missed (determined by motor stall detection) before + motor is considered to have stalled. (default: 5)`); + + export const MISSED_STEP_DECAY = trim(`Reduction to missed step total for every good step. (default: 5)`); export const ENCODER_SCALING = trim(`encoder scaling factor = 10000 * (motor resolution * microsteps) / (encoder resolution). (default: 5556 (10000*200/360))`); + // Hardware Settings: Endstops export const ENABLE_ENDSTOPS = trim(`Enable use of electronic end-stops for end detection, calibration and homing. (default: disabled)`); @@ -173,6 +185,18 @@ export namespace ToolTips { trim(`Invert axis end-stops. Enable for normally closed (NC), disable for normally open (NO). (default: disabled)`); + // Hardware Settings: Error Handling + export const TIMEOUT_AFTER = + trim(`Amount of time to wait for a command to execute before stopping. + (default: 120s)`); + + export const MAX_MOVEMENT_RETRIES = + trim(`Number of times to retry a movement before stopping. (default: 3)`); + + export const E_STOP_ON_MOV_ERR = + trim(`Emergency stop if movement is not complete after the maximum + number of retries. (default: disabled)`); + // Hardware Settings: Pin Guard export const PIN_GUARD_PIN_NUMBER = trim(`The number of the pin to guard. This pin will be set to the specified @@ -263,8 +287,12 @@ export namespace ToolTips { export const FIND_HOME = trim(`The Find Home step instructs the device to perform a homing - command (using encoders or endstops) to find and set zero for - the chosen axis or axes.`); + command (using encoders, stall detection, or endstops) to find and set + zero for the chosen axis or axes.`); + + export const CALIBRATE = + trim(`If encoders, stall detection, or end-stops are enabled, + home axis and determine maximum.`); export const IF = trim(`Execute a sequence if a condition is satisfied. If the condition @@ -715,8 +743,8 @@ export namespace Content { export const END_DETECTION_DISABLED = trim(`This command will not execute correctly because you do not have - encoders or endstops enabled for the chosen axis. Enable endstops or - encoders from the Device page for: `); + encoders, stall detection, or endstops enabled for the chosen axis. + Enable endstops, encoders, or stall detection from the Device page for: `); export const IN_USE = trim(`Used in another resource. Protected from deletion.`); @@ -924,8 +952,7 @@ export namespace DiagnosticMessages { but we have no recent record of FarmBot connecting to the internet. This usually happens because of poor WiFi connectivity in the garden, a bad password during configuration, a very long power outage, or - blocked ports on FarmBot's local network. Please refer IT staff to - https://software.farm.bot/docs/for-it-security-professionals`); + blocked ports on FarmBot's local network. Please refer IT staff to:`); export const NO_WS_AVAILABLE = trim(`You are either offline, using a web browser that does not support WebSockets, or are behind a firewall that diff --git a/frontend/crash_page.tsx b/frontend/crash_page.tsx index 031ff12ad..7f6b92dd7 100644 --- a/frontend/crash_page.tsx +++ b/frontend/crash_page.tsx @@ -2,6 +2,7 @@ import * as React from "react"; import { get } from "lodash"; import { Page } from "./ui/index"; import { Session } from "./session"; +import { ExternalUrl } from "./external_urls"; /** Use currying to pass down `error` object for now. */ export function crashPage(error: object) { @@ -24,7 +25,7 @@ export function crashPage(error: object) {
  • Perform a "hard refresh" (CTRL + SHIFT + R on most machines).
  • Session.clear()}>Log out by clicking here.
  • Send the error information (below) to our developer team via the - FarmBot software + FarmBot software forum. Including additional information (such as steps leading up to the error) help us identify solutions more quickly.
  • diff --git a/frontend/css/farm_designer/farm_designer_panels.scss b/frontend/css/farm_designer/farm_designer_panels.scss index a1867cc96..fea4106db 100644 --- a/frontend/css/farm_designer/farm_designer_panels.scss +++ b/frontend/css/farm_designer/farm_designer_panels.scss @@ -580,6 +580,7 @@ font-size: 1.4rem; } } + .tools-header, .tool-slots-header { display: flex; margin-top: 1rem; @@ -624,6 +625,12 @@ } } .add-stock-tools { + .filter-search { + margin-bottom: 1rem; + button { + margin-top: 0.2rem; + } + } ul { font-size: 1.2rem; padding-left: 1rem; diff --git a/frontend/css/global.scss b/frontend/css/global.scss index 8e320263b..b719f3d1c 100644 --- a/frontend/css/global.scss +++ b/frontend/css/global.scss @@ -433,14 +433,17 @@ a { } } -.pin-bindings-widget { +.pin-bindings { .fa-exclamation-triangle { color: $orange; + margin-left: 1rem; + margin-top: 0.75rem; } .fa-th-large { + position: absolute; + top: 0.75rem; + left: 0.5rem; color: $dark_gray; - margin-top: 0.5rem; - margin-left: 0.5rem; } .fb-button { &.green { @@ -449,16 +452,27 @@ a { } .bindings-list { margin-bottom: 1rem; + margin-left: 1rem; font-size: 1.2rem; } + .binding-type-dropdown { + margin-bottom: 1.5rem; + } .stock-pin-bindings-button { button { - margin: 0 !important; + margin: 1rem; + float: left; + margin-left: 2rem; } i { margin-right: 0.5rem; } } + .bp3-popover-wrapper { + display: inline; + float: none !important; + margin-left: 1rem; + } } .sensor-history-widget { @@ -1308,6 +1322,12 @@ ul { } } +.boolean-camera-calibration-config { + input[type=checkbox] { + display: block; + } +} + .tour-list { margin: auto; max-width: 300px; @@ -1519,16 +1539,24 @@ textarea:focus { box-shadow: 0 0 10px rgba(0,0,0,.2); } -.sort-path-info-bar { - background: lightgray; +.sort-option-bar { cursor: pointer; - font-size: 1.1rem; margin-top: 0.25rem; margin-bottom: 0.25rem; - white-space: nowrap; - line-height: 1.75rem; - &:hover { - background: darken(lightgray, 10%); + border: 2px solid $panel_light_blue; + &:hover, &.selected { + border: 2px solid $medium_gray; + border-radius: 2px; + .sort-path-info-bar { + background: darken($light_gray, 10%); + } + } + .sort-path-info-bar { + background: $light_gray; + font-size: 1.2rem; + padding-left: 0.5rem; + white-space: nowrap; + line-height: 2.5rem; } } diff --git a/frontend/css/image_flipper.scss b/frontend/css/image_flipper.scss index 48573d577..99c9f8747 100644 --- a/frontend/css/image_flipper.scss +++ b/frontend/css/image_flipper.scss @@ -97,6 +97,7 @@ } } +.camera-calibration, .weed-detector{ .farmware-button{ position: relative; diff --git a/frontend/demo/demo_iframe.tsx b/frontend/demo/demo_iframe.tsx index e0f5ba72c..16681552f 100644 --- a/frontend/demo/demo_iframe.tsx +++ b/frontend/demo/demo_iframe.tsx @@ -2,16 +2,13 @@ import { connect, MqttClient } from "mqtt"; import React from "react"; import { uuid } from "farmbot"; import axios from "axios"; +import { ExternalUrl } from "../external_urls"; interface State { error: Error | undefined; stage: string; } -const VIDEO_URL = - "https://cdn.shopify.com/s/files/1/2040/0289/files/Farm_Designer_Loop.mp4?9552037556691879018"; -const PHONE_URL = - "https://cdn.shopify.com/s/files/1/2040/0289/files/Controls.png?9668345515035078097"; const WS_CONFIG = { username: "farmbot_demo", password: "required, but not used.", @@ -63,9 +60,9 @@ export class DemoIframe extends React.Component<{}, State> { return
    - + diff --git a/frontend/devices/__tests__/actions_test.ts b/frontend/devices/__tests__/actions_test.ts index ad82f57be..2d0e5a1cd 100644 --- a/frontend/devices/__tests__/actions_test.ts +++ b/frontend/devices/__tests__/actions_test.ts @@ -307,7 +307,7 @@ describe("commandErr()", () => { }); }); -describe("toggleControlPanel()", function () { +describe("toggleControlPanel()", () => { it("toggles", () => { const action = actions.toggleControlPanel("homing_and_calibration"); expect(action.payload).toEqual("homing_and_calibration"); diff --git a/frontend/devices/__tests__/state_to_props_test.tsx b/frontend/devices/__tests__/state_to_props_test.tsx index 130b5ab5c..4fbfe4d22 100644 --- a/frontend/devices/__tests__/state_to_props_test.tsx +++ b/frontend/devices/__tests__/state_to_props_test.tsx @@ -35,7 +35,6 @@ describe("mapStateToProps()", () => { it("uses the API as the source of FBOS settings", () => { const fakeApiConfig = fakeFbosConfig(); fakeApiConfig.body.auto_sync = true; - fakeApiConfig.body.api_migrated = true; mockFbosConfig = fakeApiConfig; const props = mapStateToProps(fakeState()); expect(props.sourceFbosConfig("auto_sync")).toEqual({ @@ -53,19 +52,6 @@ describe("mapStateToProps()", () => { }); }); - it("uses the bot as the source of FBOS settings: ignore API defaults", () => { - const state = fakeState(); - state.bot.hardware.configuration.auto_sync = false; - const fakeApiConfig = fakeFbosConfig(); - fakeApiConfig.body.auto_sync = true; - fakeApiConfig.body.api_migrated = false; - mockFbosConfig = fakeApiConfig; - const props = mapStateToProps(state); - expect(props.sourceFbosConfig("auto_sync")).toEqual({ - value: false, consistent: true - }); - }); - it("returns API Farmware env vars", () => { const state = fakeState(); state.bot.hardware.user_env = {}; diff --git a/frontend/devices/actions.ts b/frontend/devices/actions.ts index 30cbcaab6..edea6ce0f 100644 --- a/frontend/devices/actions.ts +++ b/frontend/devices/actions.ts @@ -26,11 +26,6 @@ import { t } from "../i18next_wrapper"; const ON = 1, OFF = 0; export type ConfigKey = keyof McuParams; -export const EXPECTED_MAJOR = 6; -export const EXPECTED_MINOR = 0; -export const FEATURE_MIN_VERSIONS_URL = - "https://raw.githubusercontent.com/FarmBot/farmbot_os/staging/" + - "FEATURE_MIN_VERSIONS.json"; // Already filtering messages in FarmBot OS and the API- this is just for // an additional layer of safety. const BAD_WORDS = ["WPA", "PSK", "PASSWORD", "NERVES"]; @@ -132,7 +127,7 @@ export function sync(): Thunk { return function (_dispatch, getState) { const currentFBOSversion = getState().bot.hardware.informational_settings.controller_version; - const IS_OK = versionOK(currentFBOSversion, EXPECTED_MAJOR, EXPECTED_MINOR); + const IS_OK = versionOK(currentFBOSversion); if (IS_OK) { getDevice() .sync() diff --git a/frontend/devices/components/__tests__/hardware_settings_test.tsx b/frontend/devices/components/__tests__/hardware_settings_test.tsx index 02e7a3001..427070cce 100644 --- a/frontend/devices/components/__tests__/hardware_settings_test.tsx +++ b/frontend/devices/components/__tests__/hardware_settings_test.tsx @@ -65,13 +65,7 @@ describe("", () => { it("shows param export menu", () => { const p = fakeProps(); p.firmwareConfig = fakeFirmwareConfig().body; - p.firmwareConfig.api_migrated = true; const wrapper = shallow(); expect(wrapper.html()).toContain("fa-download"); }); - - it("doesn't show param export menu", () => { - const wrapper = shallow(); - expect(wrapper.html()).not.toContain("fa-download"); - }); }); diff --git a/frontend/devices/components/farmbot_os_settings.tsx b/frontend/devices/components/farmbot_os_settings.tsx index 9fceb9231..cbfa9d10f 100644 --- a/frontend/devices/components/farmbot_os_settings.tsx +++ b/frontend/devices/components/farmbot_os_settings.tsx @@ -4,7 +4,7 @@ import { t } from "../../i18next_wrapper"; import { FarmbotOsProps, FarmbotOsState, Feature } from "../interfaces"; import { Widget, WidgetHeader, WidgetBody, Row, Col } from "../../ui"; import { save, edit } from "../../api/crud"; -import { MustBeOnline, isBotOnline } from "../must_be_online"; +import { isBotOnline } from "../must_be_online"; import { Content } from "../../constants"; import { TimezoneSelector } from "../timezones/timezone_selector"; import { timezoneMismatch } from "../timezones/guess_timezone"; @@ -15,6 +15,7 @@ import { AutoUpdateRow } from "./fbos_settings/auto_update_row"; import { AutoSyncRow } from "./fbos_settings/auto_sync_row"; import { PowerAndReset } from "./fbos_settings/power_and_reset"; import { BootSequenceSelector } from "./fbos_settings/boot_sequence_selector"; +import { ExternalUrl } from "../../external_urls"; export enum ColWidth { label = 3, @@ -22,15 +23,12 @@ export enum ColWidth { button = 2 } -const OS_RELEASE_NOTES_URL = - "https://raw.githubusercontent.com/FarmBot/farmbot_os/staging/RELEASE_NOTES.md"; - export class FarmbotOsSettings extends React.Component { state: FarmbotOsState = { allOsReleaseNotes: "" }; componentDidMount() { - this.fetchReleaseNotes(OS_RELEASE_NOTES_URL); + this.fetchReleaseNotes(ExternalUrl.osReleaseNotes); } get osMajorVersion() { @@ -116,54 +114,46 @@ export class FarmbotOsSettings
    - - - - - - - {this.props.shouldDisplay(Feature.boot_sequence) && - } - - + + + + + + {this.props.shouldDisplay(Feature.boot_sequence) && + } + ; diff --git a/frontend/devices/components/fbos_settings/__tests__/auto_update_row_test.tsx b/frontend/devices/components/fbos_settings/__tests__/auto_update_row_test.tsx index 09b5e0d91..5d6462d25 100644 --- a/frontend/devices/components/fbos_settings/__tests__/auto_update_row_test.tsx +++ b/frontend/devices/components/fbos_settings/__tests__/auto_update_row_test.tsx @@ -21,7 +21,6 @@ describe("", () => { const fakeProps = (): AutoUpdateRowProps => ({ timeFormat: "12h", - shouldDisplay: jest.fn(() => true), device: fakeDevice(), dispatch: jest.fn(x => x(jest.fn(), () => state)), sourceFbosConfig: () => ({ value: 1, consistent: true }) diff --git a/frontend/devices/components/fbos_settings/__tests__/board_type_test.tsx b/frontend/devices/components/fbos_settings/__tests__/board_type_test.tsx index 2532f9022..4e623edc5 100644 --- a/frontend/devices/components/fbos_settings/__tests__/board_type_test.tsx +++ b/frontend/devices/components/fbos_settings/__tests__/board_type_test.tsx @@ -69,6 +69,9 @@ describe("", () => { { label: "Arduino/RAMPS (Genesis v1.2)", value: "arduino" }, { label: "Farmduino (Genesis v1.3)", value: "farmduino" }, { label: "Farmduino (Genesis v1.4)", value: "farmduino_k14" }, + { label: "Farmduino (Genesis v1.5)", value: "farmduino_k15" }, + { label: "Farmduino (Express v1.0)", value: "express_k10" }, + { label: "None", value: "none" }, ]); }); diff --git a/frontend/devices/components/fbos_settings/__tests__/firmware_hardware_status_test.tsx b/frontend/devices/components/fbos_settings/__tests__/firmware_hardware_status_test.tsx index f63470754..38d8a6ccd 100644 --- a/frontend/devices/components/fbos_settings/__tests__/firmware_hardware_status_test.tsx +++ b/frontend/devices/components/fbos_settings/__tests__/firmware_hardware_status_test.tsx @@ -22,7 +22,6 @@ describe("", () => { apiFirmwareValue: undefined, botFirmwareValue: undefined, mcuFirmwareValue: undefined, - shouldDisplay: () => true, timeSettings: fakeTimeSettings(), dispatch: jest.fn(), }); @@ -79,7 +78,6 @@ describe("", () => { alerts: [], botOnline: true, apiFirmwareValue: undefined, - shouldDisplay: () => true, timeSettings: fakeTimeSettings(), dispatch: jest.fn(), }); diff --git a/frontend/devices/components/fbos_settings/__tests__/power_and_reset_test.tsx b/frontend/devices/components/fbos_settings/__tests__/power_and_reset_test.tsx index d993e3dc0..147e2be34 100644 --- a/frontend/devices/components/fbos_settings/__tests__/power_and_reset_test.tsx +++ b/frontend/devices/components/fbos_settings/__tests__/power_and_reset_test.tsx @@ -25,30 +25,25 @@ describe("", () => { const state = fakeState(); state.resources = buildResourceIndex([fakeConfig]); - const fakeProps = (): PowerAndResetProps => { - return { - controlPanelState: panelState(), - dispatch: jest.fn(x => x(jest.fn(), () => state)), - sourceFbosConfig: () => ({ value: true, consistent: true }), - shouldDisplay: jest.fn(), - botOnline: true, - }; - }; + const fakeProps = (): PowerAndResetProps => ({ + controlPanelState: panelState(), + dispatch: jest.fn(x => x(jest.fn(), () => state)), + sourceFbosConfig: () => ({ value: true, consistent: true }), + botOnline: true, + }); - it("open", () => { + it("renders in open state", () => { const p = fakeProps(); p.controlPanelState.power_and_reset = true; const wrapper = mount(); - ["Power and Reset", "Restart", "Shutdown", "Factory Reset", - "Automatic Factory Reset", "Connection Attempt Period", "Change Ownership"] + ["Power and Reset", "Restart", "Shutdown", "Restart Firmware", + "Factory Reset", "Automatic Factory Reset", + "Connection Attempt Period", "Change Ownership"] .map(string => expect(wrapper.text().toLowerCase()) .toContain(string.toLowerCase())); - ["Restart Firmware"] - .map(string => expect(wrapper.text().toLowerCase()) - .not.toContain(string.toLowerCase())); }); - it("closed", () => { + it("renders as closed", () => { const p = fakeProps(); p.controlPanelState.power_and_reset = false; const wrapper = mount(); @@ -73,7 +68,7 @@ describe("", () => { p.sourceFbosConfig = () => ({ value: false, consistent: true }); p.controlPanelState.power_and_reset = true; const wrapper = mount(); - clickButton(wrapper, 3, "yes"); + clickButton(wrapper, 4, "yes"); expect(edit).toHaveBeenCalledWith(fakeConfig, { disable_factory_reset: true }); expect(save).toHaveBeenCalledWith(fakeConfig.uuid); }); @@ -81,7 +76,6 @@ describe("", () => { it("restarts firmware", () => { const p = fakeProps(); p.controlPanelState.power_and_reset = true; - p.shouldDisplay = () => true; const wrapper = mount(); expect(wrapper.text().toLowerCase()) .toContain("Restart Firmware".toLowerCase()); diff --git a/frontend/devices/components/fbos_settings/auto_update_row.tsx b/frontend/devices/components/fbos_settings/auto_update_row.tsx index b83ca5c32..f99e553f1 100644 --- a/frontend/devices/components/fbos_settings/auto_update_row.tsx +++ b/frontend/devices/components/fbos_settings/auto_update_row.tsx @@ -7,17 +7,16 @@ import { Content } from "../../../constants"; import { AutoUpdateRowProps } from "./interfaces"; import { t } from "../../../i18next_wrapper"; import { OtaTimeSelector, changeOtaHour } from "./ota_time_selector"; -import { Feature } from "../../interfaces"; export function AutoUpdateRow(props: AutoUpdateRowProps) { const osAutoUpdate = props.sourceFbosConfig("os_auto_update"); return
    - {props.shouldDisplay(Feature.ota_update_hour) && } + onChange={changeOtaHour(props.dispatch, props.device)} />
    @@ -69,8 +69,7 @@ export class BoardType extends React.Component { alerts={this.props.alerts} bot={this.props.bot} dispatch={this.props.dispatch} - timeSettings={this.props.timeSettings} - shouldDisplay={this.props.shouldDisplay} /> + timeSettings={this.props.timeSettings} /> ; } diff --git a/frontend/devices/components/fbos_settings/fbos_details.tsx b/frontend/devices/components/fbos_settings/fbos_details.tsx index 43ad6a0c7..c06a4b627 100644 --- a/frontend/devices/components/fbos_settings/fbos_details.tsx +++ b/frontend/devices/components/fbos_settings/fbos_details.tsx @@ -14,6 +14,7 @@ import { timeFormatString } from "../../../util"; import { TimeSettings } from "../../../interfaces"; import { StringConfigKey } from "farmbot/dist/resources/configs/fbos"; import { boardType, FIRMWARE_CHOICES_DDI } from "../firmware_hardware_support"; +import { ExternalUrl, FarmBotRepo } from "../../../external_urls"; /** Return an indicator color for the given temperature (C). */ export const colorFromTemp = (temp: number | undefined): string => { @@ -170,7 +171,7 @@ const shortenCommit = (longCommit: string) => (longCommit || "").slice(0, 8); interface CommitDisplayProps { title: string; - repo: string; + repo: FarmBotRepo; commit: string; } @@ -184,7 +185,7 @@ const CommitDisplay = ( {shortCommit === "---" ? shortCommit : {shortCommit} } @@ -270,14 +271,15 @@ export function FbosDetails(props: FbosDetailsProps) { timeSettings={props.timeSettings} device={props.deviceAccount} />

    {t("Environment")}: {env}

    - +

    {t("Target")}: {target}

    {t("Node name")}: {last((node_name || "").split("@"))}

    {t("Device ID")}: {props.deviceAccount.body.id}

    {isString(private_ip) &&

    {t("Local IP address")}: {private_ip}

    }

    {t("Firmware")}: {reformatFwVersion(firmware_version)}

    + repo={FarmBotRepo.FarmBotArduinoFirmware} commit={firmwareCommit} />

    {t("Firmware code")}: {firmware_version}

    {isNumber(uptime) && } {isNumber(memory_usage) && diff --git a/frontend/devices/components/fbos_settings/firmware_hardware_status.tsx b/frontend/devices/components/fbos_settings/firmware_hardware_status.tsx index a355d02b3..6056d2df1 100644 --- a/frontend/devices/components/fbos_settings/firmware_hardware_status.tsx +++ b/frontend/devices/components/fbos_settings/firmware_hardware_status.tsx @@ -3,7 +3,7 @@ import { Popover, Position } from "@blueprintjs/core"; import { FIRMWARE_CHOICES_DDI } from "../firmware_hardware_support"; import { flashFirmware } from "../../actions"; import { t } from "../../../i18next_wrapper"; -import { BotState, Feature, ShouldDisplay } from "../../interfaces"; +import { BotState } from "../../interfaces"; import { FirmwareAlerts } from "../../../messages/alerts"; import { TimeSettings } from "../../../interfaces"; import { trim } from "../../../util"; @@ -36,7 +36,6 @@ export interface FirmwareHardwareStatusDetailsProps { apiFirmwareValue: string | undefined; botFirmwareValue: string | undefined; mcuFirmwareValue: string | undefined; - shouldDisplay: ShouldDisplay; timeSettings: TimeSettings; dispatch: Function; } @@ -81,13 +80,10 @@ export const FirmwareHardwareStatusDetails =

    {lookup(props.botFirmwareValue) || t("unknown")}

    {lookup(props.mcuFirmwareValue) || t("unknown")}

    - {props.shouldDisplay(Feature.flash_firmware) && -
    - - -
    } + + { botFirmwareValue={firmware_hardware} mcuFirmwareValue={boardType(firmware_version)} timeSettings={props.timeSettings} - dispatch={props.dispatch} - shouldDisplay={props.shouldDisplay} /> + dispatch={props.dispatch} /> ; }; diff --git a/frontend/devices/components/fbos_settings/interfaces.ts b/frontend/devices/components/fbos_settings/interfaces.ts index b3b5047e9..c9549e389 100644 --- a/frontend/devices/components/fbos_settings/interfaces.ts +++ b/frontend/devices/components/fbos_settings/interfaces.ts @@ -24,7 +24,6 @@ export interface AutoUpdateRowProps { timeFormat: PreferredHourFormat; sourceFbosConfig: SourceFbosConfig; device: TaggedDevice; - shouldDisplay: ShouldDisplay; } export interface CameraSelectionProps { @@ -53,7 +52,6 @@ export interface PowerAndResetProps { controlPanelState: ControlPanelState; dispatch: Function; sourceFbosConfig: SourceFbosConfig; - shouldDisplay: ShouldDisplay; botOnline: boolean; } diff --git a/frontend/devices/components/fbos_settings/power_and_reset.tsx b/frontend/devices/components/fbos_settings/power_and_reset.tsx index f67061e12..e6ac2d437 100644 --- a/frontend/devices/components/fbos_settings/power_and_reset.tsx +++ b/frontend/devices/components/fbos_settings/power_and_reset.tsx @@ -4,23 +4,20 @@ import { Collapse, Popover, Position } from "@blueprintjs/core"; import { FactoryResetRow } from "./factory_reset_row"; import { PowerAndResetProps } from "./interfaces"; import { ChangeOwnershipForm } from "./change_ownership_form"; -import { Feature } from "../../interfaces"; import { FbosButtonRow } from "./fbos_button_row"; import { Content } from "../../../constants"; import { reboot, powerOff, restartFirmware } from "../../actions"; import { t } from "../../../i18next_wrapper"; export function PowerAndReset(props: PowerAndResetProps) { - const { dispatch, sourceFbosConfig, shouldDisplay, botOnline } = props; + const { dispatch, sourceFbosConfig, botOnline } = props; const { power_and_reset } = props.controlPanelState; return
    -
    -
    -
    +
    - {shouldDisplay(Feature.firmware_restart) && - } + { const values: FirmwareHardware[] = [ @@ -77,12 +76,11 @@ export const FIRMWARE_CHOICES_DDI = { [NONE.value]: NONE }; -export const getFirmwareChoices = - (shouldDisplay: ShouldDisplay = () => true) => ([ - ARDUINO, - FARMDUINO, - FARMDUINO_K14, - ...(shouldDisplay(Feature.farmduino_k15) ? [FARMDUINO_K15] : []), - ...(shouldDisplay(Feature.express_k10) ? [EXPRESS_K10] : []), - ...(shouldDisplay(Feature.none_firmware) ? [NONE] : []), - ]); +export const getFirmwareChoices = () => ([ + ARDUINO, + FARMDUINO, + FARMDUINO_K14, + FARMDUINO_K15, + EXPRESS_K10, + NONE, +]); diff --git a/frontend/devices/components/hardware_settings.tsx b/frontend/devices/components/hardware_settings.tsx index a94d92497..0ad58b3cd 100644 --- a/frontend/devices/components/hardware_settings.tsx +++ b/frontend/devices/components/hardware_settings.tsx @@ -2,11 +2,12 @@ import * as React from "react"; import { MCUFactoryReset, bulkToggleControlPanel } from "../actions"; import { Widget, WidgetHeader, WidgetBody } from "../../ui/index"; import { HardwareSettingsProps } from "../interfaces"; -import { MustBeOnline, isBotOnline } from "../must_be_online"; +import { isBotOnline } from "../must_be_online"; import { ToolTips } from "../../constants"; import { DangerZone } from "./hardware_settings/danger_zone"; import { PinGuard } from "./hardware_settings/pin_guard"; -import { EncodersAndEndStops } from "./hardware_settings/encoders_and_endstops"; +import { Encoders } from "./hardware_settings/encoders"; +import { EndStops } from "./hardware_settings/endstops"; import { Motors } from "./hardware_settings/motors"; import { SpacePanelHeader } from "./hardware_settings/space_panel_header"; import { @@ -15,6 +16,8 @@ import { import { Popover, Position } from "@blueprintjs/core"; import { FwParamExportMenu } from "./hardware_settings/export_menu"; import { t } from "../../i18next_wrapper"; +import { PinBindings } from "./hardware_settings/pin_bindings"; +import { ErrorHandling } from "./hardware_settings/error_handling"; export class HardwareSettings extends React.Component { @@ -27,15 +30,9 @@ export class HardwareSettings extends const { informational_settings } = this.props.bot.hardware; const { sync_status } = informational_settings; const botDisconnected = !isBotOnline(sync_status, botToMqttStatus); + const commonProps = { dispatch, controlPanelState }; return - - - - + - {firmwareConfig && - - - - } - -
    - -
    - - - - - -
    + + + + +
    + +
    + + + + + + + +
    ; } diff --git a/frontend/devices/components/hardware_settings/__tests__/calibration_row_test.tsx b/frontend/devices/components/hardware_settings/__tests__/calibration_row_test.tsx index 9d384af5c..f1475c44a 100644 --- a/frontend/devices/components/hardware_settings/__tests__/calibration_row_test.tsx +++ b/frontend/devices/components/hardware_settings/__tests__/calibration_row_test.tsx @@ -1,22 +1,40 @@ -const mockDevice = { - calibrate: jest.fn(() => Promise.resolve({})) -}; -jest.mock("../../../../device", () => ({ - getDevice: () => (mockDevice) -})); import * as React from "react"; import { mount } from "enzyme"; import { CalibrationRow } from "../calibration_row"; import { bot } from "../../../../__test_support__/fake_state/bot"; +import { CalibrationRowProps } from "../../interfaces"; + +describe("", () => { + const fakeProps = (): CalibrationRowProps => ({ + type: "calibrate", + hardware: bot.hardware.mcu_params, + botDisconnected: false, + action: jest.fn(), + toolTip: "calibrate", + title: "calibrate", + axisTitle: "calibrate", + }); -describe("", () => { it("calls device", () => { - const result = mount(); + const p = fakeProps(); + const result = mount(); + p.hardware.encoder_enabled_x = 1; + p.hardware.encoder_enabled_y = 1; + p.hardware.encoder_enabled_z = 0; [0, 1, 2].map(i => result.find("LockableButton").at(i).simulate("click")); - expect(mockDevice.calibrate).toHaveBeenCalledTimes(2); - [{ axis: "y" }, { axis: "x" }].map(x => - expect(mockDevice.calibrate).toHaveBeenCalledWith(x)); + expect(p.action).toHaveBeenCalledTimes(2); + ["y", "x"].map(x => expect(p.action).toHaveBeenCalledWith(x)); + }); + + it("is not disabled", () => { + const p = fakeProps(); + p.type = "zero"; + const result = mount(); + p.hardware.encoder_enabled_x = 0; + p.hardware.encoder_enabled_y = 1; + p.hardware.encoder_enabled_z = 0; + [0, 1, 2].map(i => result.find("LockableButton").at(i).simulate("click")); + expect(p.action).toHaveBeenCalledTimes(3); + ["x", "y", "z"].map(x => expect(p.action).toHaveBeenCalledWith(x)); }); }); diff --git a/frontend/devices/components/hardware_settings/__tests__/encoders_and_endstops_test.tsx b/frontend/devices/components/hardware_settings/__tests__/encoders_and_endstops_test.tsx deleted file mode 100644 index 6c70916d5..000000000 --- a/frontend/devices/components/hardware_settings/__tests__/encoders_and_endstops_test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as React from "react"; -import { mount, shallow } from "enzyme"; -import { EncodersAndEndStops } from "../encoders_and_endstops"; -import { EncodersProps, NumericMCUInputGroupProps } from "../../interfaces"; -import { panelState } from "../../../../__test_support__/control_panel_state"; -import { bot } from "../../../../__test_support__/fake_state/bot"; -import { Dictionary } from "farmbot"; - -describe("", () => { - const mockFeatures: Dictionary = {}; - const fakeProps = (): EncodersProps => ({ - dispatch: jest.fn(), - controlPanelState: panelState(), - sourceFwConfig: x => - ({ value: bot.hardware.mcu_params[x], consistent: true }), - shouldDisplay: jest.fn(key => mockFeatures[key]), - firmwareHardware: undefined, - }); - - it("shows encoder labels", () => { - const p = fakeProps(); - p.firmwareHardware = undefined; - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).toContain("encoder"); - expect(wrapper.text().toLowerCase()).not.toContain("stall"); - }); - - it("shows stall labels", () => { - const p = fakeProps(); - p.firmwareHardware = "express_k10"; - const wrapper = mount(); - expect(wrapper.text().toLowerCase()).not.toContain("encoder"); - expect(wrapper.text().toLowerCase()).toContain("stall"); - }); - - it.each<["short" | "long"]>([ - ["short"], - ["long"], - ])("uses %s int scaling factor", (size) => { - mockFeatures.long_scaling_factor = size === "short" ? false : true; - const wrapper = shallow(); - const sfProps = wrapper.find("NumericMCUInputGroup").at(2) - .props() as NumericMCUInputGroupProps; - expect(sfProps.name).toEqual("Encoder Scaling"); - expect(sfProps.intSize).toEqual(size); - }); -}); diff --git a/frontend/devices/components/hardware_settings/__tests__/encoders_test.tsx b/frontend/devices/components/hardware_settings/__tests__/encoders_test.tsx new file mode 100644 index 000000000..d06e176d6 --- /dev/null +++ b/frontend/devices/components/hardware_settings/__tests__/encoders_test.tsx @@ -0,0 +1,32 @@ +import * as React from "react"; +import { mount } from "enzyme"; +import { Encoders } from "../encoders"; +import { EncodersProps } from "../../interfaces"; +import { panelState } from "../../../../__test_support__/control_panel_state"; +import { bot } from "../../../../__test_support__/fake_state/bot"; + +describe("", () => { + const fakeProps = (): EncodersProps => ({ + dispatch: jest.fn(), + controlPanelState: panelState(), + sourceFwConfig: x => + ({ value: bot.hardware.mcu_params[x], consistent: true }), + firmwareHardware: undefined, + }); + + it("shows encoder labels", () => { + const p = fakeProps(); + p.firmwareHardware = undefined; + const wrapper = mount(); + expect(wrapper.text().toLowerCase()).toContain("encoder"); + expect(wrapper.text().toLowerCase()).not.toContain("stall"); + }); + + it("shows stall labels", () => { + const p = fakeProps(); + p.firmwareHardware = "express_k10"; + const wrapper = mount(); + expect(wrapper.text().toLowerCase()).not.toContain("encoder"); + expect(wrapper.text().toLowerCase()).toContain("stall"); + }); +}); diff --git a/frontend/devices/components/hardware_settings/__tests__/endstops_test.tsx b/frontend/devices/components/hardware_settings/__tests__/endstops_test.tsx new file mode 100644 index 000000000..96a2a5b69 --- /dev/null +++ b/frontend/devices/components/hardware_settings/__tests__/endstops_test.tsx @@ -0,0 +1,21 @@ +import * as React from "react"; +import { mount } from "enzyme"; +import { EndStops } from "../endstops"; +import { EndStopsProps } from "../../interfaces"; +import { panelState } from "../../../../__test_support__/control_panel_state"; +import { bot } from "../../../../__test_support__/fake_state/bot"; + +describe("", () => { + const fakeProps = (): EndStopsProps => ({ + dispatch: jest.fn(), + controlPanelState: panelState(), + sourceFwConfig: x => + ({ value: bot.hardware.mcu_params[x], consistent: true }), + }); + + it("shows endstop labels", () => { + const p = fakeProps(); + const wrapper = mount(); + expect(wrapper.text().toLowerCase()).toContain("endstop"); + }); +}); diff --git a/frontend/devices/components/hardware_settings/__tests__/error_handling_tests.tsx b/frontend/devices/components/hardware_settings/__tests__/error_handling_tests.tsx new file mode 100644 index 000000000..aa18b27f2 --- /dev/null +++ b/frontend/devices/components/hardware_settings/__tests__/error_handling_tests.tsx @@ -0,0 +1,47 @@ +jest.mock("../../../../api/crud", () => ({ + edit: jest.fn(), + save: jest.fn(), +})); + +import * as React from "react"; +import { mount } from "enzyme"; +import { ErrorHandling } from "../error_handling"; +import { ErrorHandlingProps } from "../../interfaces"; +import { panelState } from "../../../../__test_support__/control_panel_state"; +import { bot } from "../../../../__test_support__/fake_state/bot"; +import { edit, save } from "../../../../api/crud"; +import { fakeState } from "../../../../__test_support__/fake_state"; +import { + fakeFirmwareConfig +} from "../../../../__test_support__/fake_state/resources"; +import { + buildResourceIndex +} from "../../../../__test_support__/resource_index_builder"; + +describe("", () => { + const fakeConfig = fakeFirmwareConfig(); + const state = fakeState(); + state.resources = buildResourceIndex([fakeConfig]); + const fakeProps = (): ErrorHandlingProps => ({ + dispatch: jest.fn(x => x(jest.fn(), () => state)), + controlPanelState: panelState(), + sourceFwConfig: x => + ({ value: bot.hardware.mcu_params[x], consistent: true }), + }); + + it("shows error handling labels", () => { + const p = fakeProps(); + const wrapper = mount(); + expect(wrapper.text().toLowerCase()).toContain("error handling"); + }); + + it("toggles retries e-stop parameter", () => { + const p = fakeProps(); + p.controlPanelState.error_handling = true; + p.sourceFwConfig = () => ({ value: 1, consistent: true }); + const wrapper = mount(); + wrapper.find("button").at(0).simulate("click"); + expect(edit).toHaveBeenCalledWith(fakeConfig, { param_e_stop_on_mov_err: 0 }); + expect(save).toHaveBeenCalledWith(fakeConfig.uuid); + }); +}); diff --git a/frontend/devices/components/hardware_settings/__tests__/homing_and_calibration_test.tsx b/frontend/devices/components/hardware_settings/__tests__/homing_and_calibration_test.tsx index 6fd42b781..53425a84b 100644 --- a/frontend/devices/components/hardware_settings/__tests__/homing_and_calibration_test.tsx +++ b/frontend/devices/components/hardware_settings/__tests__/homing_and_calibration_test.tsx @@ -1,7 +1,17 @@ -jest.mock("../../../actions", () => ({ updateMCU: jest.fn() })); +jest.mock("../../../actions", () => ({ + updateMCU: jest.fn(), + commandErr: jest.fn(), +})); + +const mockDevice = { + calibrate: jest.fn(() => Promise.resolve({})), + findHome: jest.fn(() => Promise.resolve({})), + setZero: jest.fn(() => Promise.resolve({})), +}; +jest.mock("../../../../device", () => ({ getDevice: () => mockDevice })); import * as React from "react"; -import { mount } from "enzyme"; +import { mount, shallow } from "enzyme"; import { HomingAndCalibration } from "../homing_and_calibration"; import { bot } from "../../../../__test_support__/fake_state/bot"; import { updateMCU } from "../../../actions"; @@ -10,20 +20,28 @@ import { } from "../../../../__test_support__/fake_state/resources"; import { error, warning } from "../../../../toast/toast"; import { inputEvent } from "../../../../__test_support__/fake_html_events"; +import { panelState } from "../../../../__test_support__/control_panel_state"; +import { HomingAndCalibrationProps } from "../../interfaces"; +import { CalibrationRow } from "../calibration_row"; describe("", () => { + const fakeProps = (): HomingAndCalibrationProps => ({ + dispatch: jest.fn(), + bot, + controlPanelState: panelState(), + sourceFwConfig: x => ({ + value: bot.hardware.mcu_params[x], consistent: true + }), + firmwareConfig: fakeFirmwareConfig().body, + botDisconnected: false, + firmwareHardware: undefined, + }); + function testAxisLengthInput( provided: string, expected: string | undefined) { - const dispatch = jest.fn(); - bot.controlPanelState.homing_and_calibration = true; - const result = mount( ({ - value: bot.hardware.mcu_params[x], consistent: true - })} - botDisconnected={false} />); + const p = fakeProps(); + p.bot.controlPanelState.homing_and_calibration = true; + const result = mount(); const e = inputEvent(provided); const input = result.find("input").first().props(); input.onChange && input.onChange(e); @@ -45,4 +63,33 @@ describe("", () => { expect(warning).not.toHaveBeenCalled(); expect(error).not.toHaveBeenCalled(); }); + + it("finds home", () => { + const wrapper = shallow(); + wrapper.find(CalibrationRow).first().props().action("x"); + expect(mockDevice.findHome).toHaveBeenCalledWith({ + axis: "x", speed: 100 + }); + }); + + it("calibrates", () => { + const wrapper = shallow(); + wrapper.find(CalibrationRow).at(1).props().action("all"); + expect(mockDevice.calibrate).toHaveBeenCalledWith({ axis: "all" }); + }); + + it("sets zero", () => { + const wrapper = shallow(); + wrapper.find(CalibrationRow).last().props().action("all"); + expect(mockDevice.setZero).toHaveBeenCalledWith("all"); + }); + + it("shows express board related labels", () => { + const p = fakeProps(); + p.firmwareHardware = "express_k10"; + p.controlPanelState.homing_and_calibration = true; + const wrapper = shallow(); + expect(wrapper.find(CalibrationRow).first().props().toolTip) + .toContain("stall detection"); + }); }); diff --git a/frontend/devices/components/hardware_settings/__tests__/homing_row_test.tsx b/frontend/devices/components/hardware_settings/__tests__/homing_row_test.tsx deleted file mode 100644 index 4287bc45f..000000000 --- a/frontend/devices/components/hardware_settings/__tests__/homing_row_test.tsx +++ /dev/null @@ -1,33 +0,0 @@ -const mockDevice = { - findHome: jest.fn(() => Promise.resolve({})) -}; - -jest.mock("../../../../device", () => ({ - getDevice: () => (mockDevice) -})); -import * as React from "react"; -import { mount } from "enzyme"; -import { HomingRow } from "../homing_row"; -import { bot } from "../../../../__test_support__/fake_state/bot"; - -describe("", () => { - it("renders three buttons", () => { - const wrapper = mount(); - const txt = wrapper.text().toUpperCase(); - ["X", "Y", "Z"].map(function (axis) { - expect(txt).toContain(`HOME ${axis}`); - }); - }); - - it("calls device", () => { - const result = mount(); - [0, 1, 2].map(i => - result.find("LockableButton").at(i).simulate("click")); - [{ axis: "x", speed: 100 }, { axis: "y", speed: 100 }].map(x => - expect(mockDevice.findHome).toHaveBeenCalledWith(x)); - }); -}); diff --git a/frontend/devices/components/hardware_settings/__tests__/motors_test.tsx b/frontend/devices/components/hardware_settings/__tests__/motors_test.tsx index fb2df2174..7b8032843 100644 --- a/frontend/devices/components/hardware_settings/__tests__/motors_test.tsx +++ b/frontend/devices/components/hardware_settings/__tests__/motors_test.tsx @@ -37,8 +37,6 @@ describe("", () => { it("renders the base case", () => { const wrapper = render(); ["Enable 2nd X Motor", - "Max Retries", - "E-Stop on Movement Error", "Max Speed (mm/s)" ].map(string => expect(wrapper.text().toLowerCase()).toContain(string.toLowerCase())); @@ -48,16 +46,14 @@ describe("", () => { const p = fakeProps(); p.firmwareHardware = "express_k10"; const wrapper = render(); - expect(wrapper.text()).toContain("Stall"); - expect(wrapper.text()).toContain("Current"); + expect(wrapper.text()).toContain("Motor Current"); }); it("doesn't show TMC parameters", () => { const p = fakeProps(); p.firmwareHardware = "farmduino"; const wrapper = render(); - expect(wrapper.text()).not.toContain("Stall"); - expect(wrapper.text()).not.toContain("Current"); + expect(wrapper.text()).not.toContain("Motor Current"); }); const testParamToggle = ( @@ -72,15 +68,6 @@ describe("", () => { expect(save).toHaveBeenCalledWith(fakeConfig.uuid); }); }; - testParamToggle("toggles retries e-stop parameter", "param_e_stop_on_mov_err", 0); - testParamToggle("toggles enable X2", "movement_secondary_motor_x", 7); - testParamToggle("toggles invert X2", "movement_secondary_motor_invert_x", 8); - - it("renders TMC params", () => { - const p = fakeProps(); - p.firmwareHardware = "express_k10"; - const wrapper = render(); - expect(wrapper.text()).toContain("Motor Current"); - expect(wrapper.text()).toContain("Stall Sensitivity"); - }); + testParamToggle("toggles enable X2", "movement_secondary_motor_x", 6); + testParamToggle("toggles invert X2", "movement_secondary_motor_invert_x", 7); }); diff --git a/frontend/devices/components/hardware_settings/__tests__/pin_bindings_test.tsx b/frontend/devices/components/hardware_settings/__tests__/pin_bindings_test.tsx new file mode 100644 index 000000000..63b63d18d --- /dev/null +++ b/frontend/devices/components/hardware_settings/__tests__/pin_bindings_test.tsx @@ -0,0 +1,22 @@ +import * as React from "react"; +import { mount } from "enzyme"; +import { PinBindings } from "../pin_bindings"; +import { PinBindingsProps } from "../../interfaces"; +import { panelState } from "../../../../__test_support__/control_panel_state"; +import { + buildResourceIndex +} from "../../../../__test_support__/resource_index_builder"; + +describe("", () => { + const fakeProps = (): PinBindingsProps => ({ + dispatch: jest.fn(), + controlPanelState: panelState(), + resources: buildResourceIndex([]).index, + }); + + it("shows pin binding labels", () => { + const p = fakeProps(); + const wrapper = mount(); + expect(wrapper.text().toLowerCase()).toContain("pin bindings"); + }); +}); diff --git a/frontend/devices/components/hardware_settings/__tests__/zero_row_test.tsx b/frontend/devices/components/hardware_settings/__tests__/zero_row_test.tsx deleted file mode 100644 index 280578bc4..000000000 --- a/frontend/devices/components/hardware_settings/__tests__/zero_row_test.tsx +++ /dev/null @@ -1,19 +0,0 @@ -const mockDevice = { - setZero: jest.fn(() => Promise.resolve()) -}; -jest.mock("../../../../device", () => ({ - getDevice: () => (mockDevice) -})); -import * as React from "react"; -import { mount } from "enzyme"; -import { ZeroRow } from "../zero_row"; - -describe("", () => { - it("calls device", () => { - const result = mount(); - [0, 1, 2].map(i => result.find("ZeroButton").at(i).simulate("click")); - ["x", "y", "z"].map(x => - expect(mockDevice.setZero).toHaveBeenCalledWith(x)); - expect(mockDevice.setZero).toHaveBeenCalledTimes(3); - }); -}); diff --git a/frontend/devices/components/hardware_settings/calibration_row.tsx b/frontend/devices/components/hardware_settings/calibration_row.tsx index 62c7cb31c..ab986afec 100644 --- a/frontend/devices/components/hardware_settings/calibration_row.tsx +++ b/frontend/devices/components/hardware_settings/calibration_row.tsx @@ -1,19 +1,11 @@ import * as React from "react"; -import { getDevice } from "../../../device"; -import { Axis } from "../../interfaces"; import { LockableButton } from "../lockable_button"; import { axisTrackingStatus } from "../axis_tracking_status"; -import { ToolTips } from "../../../constants"; import { Row, Col, Help } from "../../../ui/index"; import { CalibrationRowProps } from "../interfaces"; -import { commandErr } from "../../actions"; import { t } from "../../../i18next_wrapper"; import { Position } from "@blueprintjs/core"; -const calibrate = (axis: Axis) => getDevice() - .calibrate({ axis }) - .catch(commandErr("Calibration")); - export function CalibrationRow(props: CalibrationRowProps) { const { hardware, botDisconnected } = props; @@ -21,18 +13,20 @@ export function CalibrationRow(props: CalibrationRowProps) { return - + {axisTrackingStatus(hardware) .map(row => { - const { axis, disabled } = row; + const { axis } = row; + const hardwareDisabled = props.type == "zero" ? false : row.disabled; return calibrate(axis)}> - {t("CALIBRATE {{axis}}", { axis })} + disabled={hardwareDisabled || botDisconnected} + onClick={() => props.action(axis)}> + {`${t(props.axisTitle)} ${axis}`} ; })} diff --git a/frontend/devices/components/hardware_settings/encoders_and_endstops.tsx b/frontend/devices/components/hardware_settings/encoders.tsx similarity index 55% rename from frontend/devices/components/hardware_settings/encoders_and_endstops.tsx rename to frontend/devices/components/hardware_settings/encoders.tsx index 72bdce981..7addae3ae 100644 --- a/frontend/devices/components/hardware_settings/encoders_and_endstops.tsx +++ b/frontend/devices/components/hardware_settings/encoders.tsx @@ -5,41 +5,53 @@ import { NumericMCUInputGroup } from "../numeric_mcu_input_group"; import { EncodersProps } from "../interfaces"; import { Header } from "./header"; import { Collapse } from "@blueprintjs/core"; -import { Feature } from "../../interfaces"; import { t } from "../../../i18next_wrapper"; import { isExpressBoard } from "../firmware_hardware_support"; -export function EncodersAndEndStops(props: EncodersProps) { +export function Encoders(props: EncodersProps) { - const { encoders_and_endstops } = props.controlPanelState; - const { dispatch, sourceFwConfig, shouldDisplay, firmwareHardware } = props; + const { encoders } = props.controlPanelState; + const { dispatch, sourceFwConfig, firmwareHardware } = props; const encodersDisabled = { x: !sourceFwConfig("encoder_enabled_x").value, y: !sourceFwConfig("encoder_enabled_y").value, z: !sourceFwConfig("encoder_enabled_z").value }; + const isExpress = isExpressBoard(firmwareHardware); return
    - + - {!isExpressBoard(firmwareHardware) && + {isExpress && + } + {!isExpress && } - {!isExpressBoard(firmwareHardware) && + {!isExpress && } - {!isExpressBoard(firmwareHardware) && + {!isExpress && } - - -
    ; } diff --git a/frontend/devices/components/hardware_settings/endstops.tsx b/frontend/devices/components/hardware_settings/endstops.tsx new file mode 100644 index 000000000..ffa1b5f3d --- /dev/null +++ b/frontend/devices/components/hardware_settings/endstops.tsx @@ -0,0 +1,57 @@ +import * as React from "react"; +import { BooleanMCUInputGroup } from "../boolean_mcu_input_group"; +import { ToolTips } from "../../../constants"; +import { EndStopsProps } from "../interfaces"; +import { Header } from "./header"; +import { Collapse } from "@blueprintjs/core"; +import { t } from "../../../i18next_wrapper"; + +export function EndStops(props: EndStopsProps) { + + const { endstops } = props.controlPanelState; + const { dispatch, sourceFwConfig } = props; + + return
    +
    + + + + + +
    ; +} diff --git a/frontend/devices/components/hardware_settings/error_handling.tsx b/frontend/devices/components/hardware_settings/error_handling.tsx new file mode 100644 index 000000000..e8d4142f6 --- /dev/null +++ b/frontend/devices/components/hardware_settings/error_handling.tsx @@ -0,0 +1,53 @@ +import * as React from "react"; +import { NumericMCUInputGroup } from "../numeric_mcu_input_group"; +import { ToolTips } from "../../../constants"; +import { ErrorHandlingProps } from "../interfaces"; +import { Header } from "./header"; +import { Collapse } from "@blueprintjs/core"; +import { t } from "../../../i18next_wrapper"; +import { McuInputBox } from "../mcu_input_box"; +import { settingToggle } from "../../actions"; +import { SingleSettingRow } from "./single_setting_row"; +import { ToggleButton } from "../../../controls/toggle_button"; + +export function ErrorHandling(props: ErrorHandlingProps) { + + const { error_handling } = props.controlPanelState; + const { dispatch, sourceFwConfig } = props; + const eStopOnMoveError = sourceFwConfig("param_e_stop_on_mov_err"); + + return
    +
    + + + + + + + dispatch( + settingToggle("param_e_stop_on_mov_err", sourceFwConfig))} /> + + +
    ; +} diff --git a/frontend/devices/components/hardware_settings/export_menu.tsx b/frontend/devices/components/hardware_settings/export_menu.tsx index 67546b166..512e9e2de 100644 --- a/frontend/devices/components/hardware_settings/export_menu.tsx +++ b/frontend/devices/components/hardware_settings/export_menu.tsx @@ -25,7 +25,7 @@ const getSubKeyName = (key: string) => { }; export const FwParamExportMenu = - ({ firmwareConfig }: { firmwareConfig: FirmwareConfig }) => { + ({ firmwareConfig }: { firmwareConfig: FirmwareConfig | undefined }) => { /** Filter out unnecessary parameters. */ const filteredConfig = pickBy(firmwareConfig, (_, key) => !["id", "device_id", "api_migrated", "created_at", "updated_at", diff --git a/frontend/devices/components/hardware_settings/homing_and_calibration.tsx b/frontend/devices/components/hardware_settings/homing_and_calibration.tsx index 7775539a8..1602fa351 100644 --- a/frontend/devices/components/hardware_settings/homing_and_calibration.tsx +++ b/frontend/devices/components/hardware_settings/homing_and_calibration.tsx @@ -2,19 +2,23 @@ import * as React from "react"; import { BooleanMCUInputGroup } from "../boolean_mcu_input_group"; import { ToolTips } from "../../../constants"; import { NumericMCUInputGroup } from "../numeric_mcu_input_group"; -import { HomingRow } from "./homing_row"; import { CalibrationRow } from "./calibration_row"; -import { ZeroRow } from "./zero_row"; import { disabledAxisMap } from "../axis_tracking_status"; import { HomingAndCalibrationProps } from "../interfaces"; import { Header } from "./header"; import { Collapse } from "@blueprintjs/core"; import { t } from "../../../i18next_wrapper"; import { calculateScale } from "./motors"; +import { isExpressBoard } from "../firmware_hardware_support"; +import { getDevice } from "../../../device"; +import { commandErr } from "../../actions"; +import { CONFIG_DEFAULTS } from "farmbot/dist/config"; export function HomingAndCalibration(props: HomingAndCalibrationProps) { - const { dispatch, bot, sourceFwConfig, firmwareConfig, botDisconnected + const { + dispatch, bot, sourceFwConfig, firmwareConfig, botDisconnected, + firmwareHardware } = props; const hardware = firmwareConfig ? firmwareConfig : bot.hardware.mcu_params; const { homing_and_calibration } = props.bot.controlPanelState; @@ -34,12 +38,43 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) { dispatch={dispatch} expanded={homing_and_calibration} /> - - - + getDevice() + .findHome({ speed: CONFIG_DEFAULTS.speed, axis }) + .catch(commandErr("'Find Home' request"))} + hardware={hardware} + botDisconnected={botDisconnected} /> + getDevice().calibrate({ axis }) + .catch(commandErr("Calibration"))} + hardware={hardware} + botDisconnected={botDisconnected} /> + getDevice().setZero(axis) + .catch(commandErr("Zeroing"))} + hardware={hardware} + botDisconnected={botDisconnected} /> -
    ; } diff --git a/frontend/devices/components/hardware_settings/homing_row.tsx b/frontend/devices/components/hardware_settings/homing_row.tsx deleted file mode 100644 index bac27807c..000000000 --- a/frontend/devices/components/hardware_settings/homing_row.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import * as React from "react"; -import { HomingRowProps } from "../interfaces"; -import { LockableButton } from "../lockable_button"; -import { axisTrackingStatus } from "../axis_tracking_status"; -import { ToolTips } from "../../../constants"; -import { Row, Col, Help } from "../../../ui/index"; -import { CONFIG_DEFAULTS } from "farmbot/dist/config"; -import { commandErr } from "../../actions"; -import { Axis } from "../../interfaces"; -import { getDevice } from "../../../device"; -import { t } from "../../../i18next_wrapper"; -import { Position } from "@blueprintjs/core"; - -const speed = CONFIG_DEFAULTS.speed; -const findHome = (axis: Axis) => getDevice() - .findHome({ speed, axis }) - .catch(commandErr("'Find Home' request")); - -export function HomingRow(props: HomingRowProps) { - const { hardware, botDisconnected } = props; - - return - - - - - {axisTrackingStatus(hardware) - .map((row) => { - const { axis, disabled } = row; - return - findHome(axis)}> - {t("FIND HOME {{axis}}", { axis })} - - ; - })} - ; -} diff --git a/frontend/devices/components/hardware_settings/motors.tsx b/frontend/devices/components/hardware_settings/motors.tsx index d76d6987e..b6281e751 100644 --- a/frontend/devices/components/hardware_settings/motors.tsx +++ b/frontend/devices/components/hardware_settings/motors.tsx @@ -5,32 +5,14 @@ import { ToggleButton } from "../../../controls/toggle_button"; import { settingToggle } from "../../actions"; import { NumericMCUInputGroup } from "../numeric_mcu_input_group"; import { MotorsProps } from "../interfaces"; -import { Row, Col, Help } from "../../../ui/index"; import { Header } from "./header"; -import { Collapse, Position } from "@blueprintjs/core"; -import { McuInputBox } from "../mcu_input_box"; +import { Collapse } from "@blueprintjs/core"; import { t } from "../../../i18next_wrapper"; import { Xyz, McuParamName } from "farmbot"; import { SourceFwConfig } from "../../interfaces"; import { calcMicrostepsPerMm } from "../../../controls/move/direction_axes_props"; -import { isTMCBoard, isExpressBoard } from "../firmware_hardware_support"; - -const SingleSettingRow = - ({ label, tooltip, settingType, children }: { - label: string, - tooltip: string, - children: React.ReactChild, - settingType: "button" | "input", - }) => - - - - - - {settingType === "button" - ? {children} - : {children}} - ; +import { isTMCBoard } from "../firmware_hardware_support"; +import { SingleSettingRow } from "./single_setting_row"; export const calculateScale = (sourceFwConfig: SourceFwConfig): Record => { @@ -51,13 +33,8 @@ export function Motors(props: MotorsProps) { } = props; const enable2ndXMotor = sourceFwConfig("movement_secondary_motor_x"); const invert2ndXMotor = sourceFwConfig("movement_secondary_motor_invert_x"); - const eStopOnMoveError = sourceFwConfig("param_e_stop_on_mov_err"); const scale = calculateScale(sourceFwConfig); - const encodersDisabled = { - x: !sourceFwConfig("encoder_enabled_x").value, - y: !sourceFwConfig("encoder_enabled_y").value, - z: !sourceFwConfig("encoder_enabled_z").value, - }; + return
    - - - - - dispatch( - settingToggle("param_e_stop_on_mov_err", sourceFwConfig))} /> - } - {isExpressBoard(firmwareHardware) && - } diff --git a/frontend/devices/components/hardware_settings/pin_bindings.tsx b/frontend/devices/components/hardware_settings/pin_bindings.tsx new file mode 100644 index 000000000..e7e716c0c --- /dev/null +++ b/frontend/devices/components/hardware_settings/pin_bindings.tsx @@ -0,0 +1,22 @@ +import * as React from "react"; +import { PinBindingsProps } from "../interfaces"; +import { Header } from "./header"; +import { Collapse } from "@blueprintjs/core"; +import { PinBindingsContent } from "../../pin_bindings/pin_bindings"; + +export function PinBindings(props: PinBindingsProps) { + + const { pin_bindings } = props.controlPanelState; + const { dispatch, resources } = props; + + return
    +
    + + + +
    ; +} diff --git a/frontend/devices/components/hardware_settings/single_setting_row.tsx b/frontend/devices/components/hardware_settings/single_setting_row.tsx new file mode 100644 index 000000000..02075c813 --- /dev/null +++ b/frontend/devices/components/hardware_settings/single_setting_row.tsx @@ -0,0 +1,20 @@ +import * as React from "react"; +import { Row, Col, Help } from "../../../ui/index"; +import { Position } from "@blueprintjs/core"; + +export const SingleSettingRow = + ({ label, tooltip, settingType, children }: { + label: string, + tooltip: string, + children: React.ReactChild, + settingType: "button" | "input", + }) => + + + + + + {settingType === "button" + ? {children} + : {children}} + ; diff --git a/frontend/devices/components/hardware_settings/zero_row.tsx b/frontend/devices/components/hardware_settings/zero_row.tsx deleted file mode 100644 index 809fffed5..000000000 --- a/frontend/devices/components/hardware_settings/zero_row.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import * as React from "react"; -import { getDevice } from "../../../device"; -import { Axis } from "../../interfaces"; -import { ToolTips } from "../../../constants"; -import { Row, Col, Help } from "../../../ui/index"; -import { ZeroRowProps } from "../interfaces"; -import { commandErr } from "../../actions"; -import { t } from "../../../i18next_wrapper"; -import { Position } from "@blueprintjs/core"; - -const zero = - (axis: Axis) => getDevice().setZero(axis).catch(commandErr("Zeroing")); -const AXES: Axis[] = ["x", "y", "z"]; - -export function ZeroButton(props: { axis: Axis; disabled: boolean; }) { - const { axis, disabled } = props; - return ; -} - -export function ZeroRow({ botDisconnected }: ZeroRowProps) { - return - - - - - {AXES.map((axis) => { - return - - ; - })} - ; -} diff --git a/frontend/devices/components/interfaces.ts b/frontend/devices/components/interfaces.ts index 75fd5141e..a76dd30ec 100644 --- a/frontend/devices/components/interfaces.ts +++ b/frontend/devices/components/interfaces.ts @@ -1,17 +1,12 @@ import { BotState, Xyz, SourceFwConfig, - ControlPanelState, ShouldDisplay + ControlPanelState, Axis } from "../interfaces"; import { McuParamName, McuParams, FirmwareHardware } from "farmbot/dist"; import { IntegerSize } from "../../util"; import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware"; import { ResourceIndex } from "../../resources/interfaces"; -export interface HomingRowProps { - hardware: McuParams; - botDisconnected: boolean; -} - export interface ZeroRowProps { botDisconnected: boolean; } @@ -19,9 +14,11 @@ export interface ZeroRowProps { export interface HomingAndCalibrationProps { dispatch: Function; bot: BotState; + controlPanelState: ControlPanelState; sourceFwConfig: SourceFwConfig; firmwareConfig: FirmwareConfig | undefined; botDisconnected: boolean; + firmwareHardware: FirmwareHardware | undefined; } export interface BooleanMCUInputGroupProps { @@ -39,8 +36,13 @@ export interface BooleanMCUInputGroupProps { } export interface CalibrationRowProps { + type: "find_home" | "calibrate" | "zero"; hardware: McuParams; botDisconnected: boolean; + action(axis: Axis): void; + toolTip: string; + title: string; + axisTitle: string; } export interface NumericMCUInputGroupProps { @@ -85,12 +87,29 @@ export interface MotorsProps { export interface EncodersProps { dispatch: Function; - shouldDisplay: ShouldDisplay; controlPanelState: ControlPanelState; sourceFwConfig: SourceFwConfig; firmwareHardware: FirmwareHardware | undefined; } +export interface EndStopsProps { + dispatch: Function; + controlPanelState: ControlPanelState; + sourceFwConfig: SourceFwConfig; +} + +export interface ErrorHandlingProps { + dispatch: Function; + controlPanelState: ControlPanelState; + sourceFwConfig: SourceFwConfig; +} + +export interface PinBindingsProps { + dispatch: Function; + controlPanelState: ControlPanelState; + resources: ResourceIndex; +} + export interface DangerZoneProps { dispatch: Function; controlPanelState: ControlPanelState; diff --git a/frontend/devices/components/pin_number_dropdown.tsx b/frontend/devices/components/pin_number_dropdown.tsx index a706c1f36..7cb4a454e 100644 --- a/frontend/devices/components/pin_number_dropdown.tsx +++ b/frontend/devices/components/pin_number_dropdown.tsx @@ -56,8 +56,12 @@ const pinNumOrNamedPin = } : pin; +const DISABLE_DDI = (): DropDownItem => ({ + label: t("None"), value: 0 +}); + const listItems = (resources: ResourceIndex): DropDownItem[] => - [...peripheralItems(resources), ...pinDropdowns(n => n)]; + [DISABLE_DDI(), ...peripheralItems(resources), ...pinDropdowns(n => n)]; const peripheralItems = (resources: ResourceIndex): DropDownItem[] => { const list = selectAllSavedPeripherals(resources) diff --git a/frontend/devices/connectivity/truth_table.ts b/frontend/devices/connectivity/truth_table.ts index 2a95f5eaf..3ff3ebc08 100644 --- a/frontend/devices/connectivity/truth_table.ts +++ b/frontend/devices/connectivity/truth_table.ts @@ -1,5 +1,11 @@ import { Dictionary } from "farmbot"; import { DiagnosticMessages } from "../../constants"; +import { docLink } from "../../ui/doc_link"; +import { trim } from "../../util/util"; + +const DiagnosticMessagesWiFiOrConfig = + trim(`${DiagnosticMessages.WIFI_OR_CONFIG} + ${docLink("for-it-security-professionals")}`); // I don't like this at all. // If anyone has a cleaner solution, I'd love to hear it. @@ -16,13 +22,13 @@ export const TRUTH_TABLE: Readonly> = { // 17: No MQTT connections. [0b10001]: DiagnosticMessages.NO_WS_AVAILABLE, // 24: Browser is connected to API and MQTT. - [0b11000]: DiagnosticMessages.WIFI_OR_CONFIG, + [0b11000]: DiagnosticMessagesWiFiOrConfig, // 9: At least the browser is connected to MQTT. - [0b01001]: DiagnosticMessages.WIFI_OR_CONFIG, + [0b01001]: DiagnosticMessagesWiFiOrConfig, // 8: At least the browser is connected to MQTT. - [0b01000]: DiagnosticMessages.WIFI_OR_CONFIG, + [0b01000]: DiagnosticMessagesWiFiOrConfig, // 25: Farmbot offline. - [0b11001]: DiagnosticMessages.WIFI_OR_CONFIG, + [0b11001]: DiagnosticMessagesWiFiOrConfig, // 2: Browser offline. Farmbot last seen by the API recently. [0b00010]: DiagnosticMessages.NO_WS_AVAILABLE, // 18: Farmbot last seen by the API recently. diff --git a/frontend/devices/devices.tsx b/frontend/devices/devices.tsx index 1d45a8e46..a0970b240 100644 --- a/frontend/devices/devices.tsx +++ b/frontend/devices/devices.tsx @@ -5,7 +5,6 @@ import { FarmbotOsSettings } from "./components/farmbot_os_settings"; import { Page, Col, Row } from "../ui/index"; import { mapStateToProps } from "./state_to_props"; import { Props } from "./interfaces"; -import { PinBindings } from "./pin_bindings/pin_bindings"; import { getStatus } from "../connectivity/reducer_support"; import { isFwHardwareValue } from "./components/firmware_hardware_support"; @@ -48,9 +47,6 @@ export class RawDevices extends React.Component { firmwareHardware={firmwareHardware} sourceFwConfig={this.props.sourceFwConfig} firmwareConfig={this.props.firmwareConfig} /> - ; diff --git a/frontend/devices/interfaces.ts b/frontend/devices/interfaces.ts index dd24b9a58..767ea8bd5 100644 --- a/frontend/devices/interfaces.ts +++ b/frontend/devices/interfaces.ts @@ -93,7 +93,7 @@ export enum Feature { variables = "variables", } -/** Object fetched from FEATURE_MIN_VERSIONS_URL. */ +/** Object fetched from ExternalUrl.featureMinVersions. */ export type MinOsFeatureLookup = Partial>; export interface BotState { @@ -245,7 +245,10 @@ export interface HardwareSettingsProps { export interface ControlPanelState { homing_and_calibration: boolean; motors: boolean; - encoders_and_endstops: boolean; + encoders: boolean; + endstops: boolean; + error_handling: boolean; + pin_bindings: boolean; danger_zone: boolean; power_and_reset: boolean; pin_guard: boolean; diff --git a/frontend/devices/pin_bindings/__tests__/list_and_label_support_test.tsx b/frontend/devices/pin_bindings/__tests__/list_and_label_support_test.tsx index 542871140..6742019cf 100644 --- a/frontend/devices/pin_bindings/__tests__/list_and_label_support_test.tsx +++ b/frontend/devices/pin_bindings/__tests__/list_and_label_support_test.tsx @@ -1,4 +1,5 @@ -import { sortByNameAndPin, ButtonPin } from "../list_and_label_support"; +import { sortByNameAndPin, ButtonPin, getSpecialActionLabel } from "../list_and_label_support"; +import { PinBindingSpecialAction } from "farmbot/dist/resources/api_resources"; describe("sortByNameAndPin()", () => { @@ -26,3 +27,11 @@ describe("sortByNameAndPin()", () => { sortTest(1, 1, Order.equal); // GPIO 1 == GPIO 1 }); }); + +describe("getSpecialActionLabel()", () => { + it("handles undefined values", () => { + expect(getSpecialActionLabel(undefined)).toEqual("None"); + expect(getSpecialActionLabel("wrong" as PinBindingSpecialAction)) + .toEqual(""); + }); +}); diff --git a/frontend/devices/pin_bindings/__tests__/pin_bindings_test.tsx b/frontend/devices/pin_bindings/__tests__/pin_bindings_test.tsx index 4d0ac230c..70dcc2781 100644 --- a/frontend/devices/pin_bindings/__tests__/pin_bindings_test.tsx +++ b/frontend/devices/pin_bindings/__tests__/pin_bindings_test.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { PinBindings } from "../pin_bindings"; +import { PinBindingsContent } from "../pin_bindings"; import { mount } from "enzyme"; import { bot } from "../../../__test_support__/fake_state/bot"; import { @@ -8,15 +8,15 @@ import { import { fakeSequence, fakePinBinding } from "../../../__test_support__/fake_state/resources"; -import { PinBindingsProps } from "../interfaces"; +import { PinBindingsContentProps } from "../interfaces"; import { SpecialPinBinding, PinBindingType, PinBindingSpecialAction } from "farmbot/dist/resources/api_resources"; -describe("", () => { - function fakeProps(): PinBindingsProps { +describe("", () => { + function fakeProps(): PinBindingsContentProps { const fakeSequence1 = fakeSequence(); fakeSequence1.body.id = 1; fakeSequence1.body.name = "Sequence 1"; @@ -51,8 +51,8 @@ describe("", () => { it("renders", () => { const p = fakeProps(); - const wrapper = mount(); - ["pin bindings", "pin number", "none", "bind", "stock bindings"] + const wrapper = mount(); + ["pin number", "none", "bind", "stock bindings"] .map(string => expect(wrapper.text().toLowerCase()).toContain(string)); ["26", "action"].map(string => expect(wrapper.text().toLowerCase()).toContain(string)); diff --git a/frontend/devices/pin_bindings/interfaces.ts b/frontend/devices/pin_bindings/interfaces.ts index 1370327f2..9f8c195e4 100644 --- a/frontend/devices/pin_bindings/interfaces.ts +++ b/frontend/devices/pin_bindings/interfaces.ts @@ -4,7 +4,7 @@ import { PinBindingSpecialAction } from "farmbot/dist/resources/api_resources"; -export interface PinBindingsProps { +export interface PinBindingsContentProps { dispatch: Function; resources: ResourceIndex; } diff --git a/frontend/devices/pin_bindings/list_and_label_support.tsx b/frontend/devices/pin_bindings/list_and_label_support.tsx index 3e6e503e3..09ea49a71 100644 --- a/frontend/devices/pin_bindings/list_and_label_support.tsx +++ b/frontend/devices/pin_bindings/list_and_label_support.tsx @@ -32,9 +32,14 @@ export const specialActionLabelLookup: { [x: string]: string } = { export const specialActionList: DropDownItem[] = Object.values(PinBindingSpecialAction) + .filter(action => action != PinBindingSpecialAction.dump_info) .map((action: PinBindingSpecialAction) => ({ label: specialActionLabelLookup[action], value: action })); +export const getSpecialActionLabel = + (action: PinBindingSpecialAction | undefined) => + specialActionLabelLookup[action || ""] || ""; + /** Pin numbers for standard buttons. */ export enum ButtonPin { estop = 16, diff --git a/frontend/devices/pin_bindings/pin_binding_input_group.tsx b/frontend/devices/pin_bindings/pin_binding_input_group.tsx index 7eb87c550..684318430 100644 --- a/frontend/devices/pin_bindings/pin_binding_input_group.tsx +++ b/frontend/devices/pin_bindings/pin_binding_input_group.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Row, Col, FBSelect, NULL_CHOICE, DropDownItem } from "../../ui"; +import { Row, Col, FBSelect, DropDownItem } from "../../ui"; import { PinBindingColWidth } from "./pin_bindings"; import { Popover, Position } from "@blueprintjs/core"; import { RpiGpioDiagram } from "./rpi_gpio_diagram"; @@ -13,9 +13,10 @@ import { pinBindingBody } from "./tagged_pin_binding_init"; import { error, warning } from "../../toast/toast"; import { validGpioPins, sysBindings, generatePinLabel, RpiPinList, - bindingTypeLabelLookup, specialActionLabelLookup, specialActionList, + bindingTypeLabelLookup, specialActionList, reservedPiGPIO, - bindingTypeList + bindingTypeList, + getSpecialActionLabel } from "./list_and_label_support"; import { SequenceSelectBox } from "../../sequences/sequence_select_box"; import { ResourceIndex } from "../../resources/interfaces"; @@ -119,8 +120,6 @@ export class PinBindingInputGroup - - {bindingType == PinBindingType.special ? - + void, }) => { const { bindingType, setBindingType } = props; - return ; diff --git a/frontend/devices/pin_bindings/pin_bindings.tsx b/frontend/devices/pin_bindings/pin_bindings.tsx index 76a085c77..f64cb3d88 100644 --- a/frontend/devices/pin_bindings/pin_bindings.tsx +++ b/frontend/devices/pin_bindings/pin_bindings.tsx @@ -1,8 +1,8 @@ import * as React from "react"; -import { Widget, WidgetBody, WidgetHeader, Row, Col } from "../../ui"; +import { Row, Col, Help } from "../../ui"; import { ToolTips } from "../../constants"; import { selectAllPinBindings } from "../../resources/selectors"; -import { PinBindingsProps, PinBindingListItems } from "./interfaces"; +import { PinBindingsContentProps, PinBindingListItems } from "./interfaces"; import { PinBindingsList } from "./pin_bindings_list"; import { PinBindingInputGroup } from "./pin_binding_input_group"; import { @@ -20,9 +20,8 @@ import { t } from "../../i18next_wrapper"; /** Width of UI columns in Pin Bindings widget. */ export enum PinBindingColWidth { pin = 4, - type = 3, - target = 4, - button = 1 + type = 6, + button = 2 } /** Use binding type to return a sequence ID or a special action. */ @@ -64,34 +63,29 @@ const PinBindingsListHeader = () => - - - + ; -export const PinBindings = (props: PinBindingsProps) => { +export const PinBindingsContent = (props: PinBindingsContentProps) => { const { dispatch, resources } = props; const pinBindings = apiPinBindings(resources); - return - + return
    + +
    {t(ToolTips.PIN_BINDING_WARNING)}
    - - - +
    +
    { pinBindings={pinBindings} dispatch={dispatch} resources={resources} /> - - ; +
    +
    ; }; diff --git a/frontend/devices/pin_bindings/pin_bindings_list.tsx b/frontend/devices/pin_bindings/pin_bindings_list.tsx index bee8c844c..431a89388 100644 --- a/frontend/devices/pin_bindings/pin_bindings_list.tsx +++ b/frontend/devices/pin_bindings/pin_bindings_list.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import { - bindingTypeLabelLookup, specialActionLabelLookup, - generatePinLabel, sortByNameAndPin + bindingTypeLabelLookup, + generatePinLabel, sortByNameAndPin, getSpecialActionLabel } from "./list_and_label_support"; import { destroy } from "../../api/crud"; import { error } from "../../toast/toast"; @@ -36,12 +36,10 @@ export const PinBindingsList = (props: PinBindingsListProps) => { {generatePinLabel(pin_number)} - {t(bindingTypeLabelLookup[binding_type || ""])} - - + {t(bindingTypeLabelLookup[binding_type || ""])}:  {sequence_id ? findSequenceById(resources, sequence_id).body.name - : t(specialActionLabelLookup[special_action || ""])} + : t(getSpecialActionLabel(special_action))}