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
-
+
-
+
{this.state.stage}
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)} />
diff --git a/frontend/devices/components/fbos_settings/board_type.tsx b/frontend/devices/components/fbos_settings/board_type.tsx
index 1f9c309f4..f5fc2fe23 100644
--- a/frontend/devices/components/fbos_settings/board_type.tsx
+++ b/frontend/devices/components/fbos_settings/board_type.tsx
@@ -57,7 +57,7 @@ export class BoardType extends React.Component {
@@ -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")}
{t("Arduino/Farmduino")}
{lookup(props.mcuFirmwareValue) || t("unknown")}
- {props.shouldDisplay(Feature.flash_firmware) &&
-
- {t("Actions")}
-
-
}
+ {t("Actions")}
+
{
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
-
-
-
-
+
dispatch(bulkToggleControlPanel(false))}>
{t("Collapse All")}
- {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
- {t("CALIBRATION")}
+ {t(props.title)}
-
+
{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
-
-
- {t("HOMING")}
-
-
-
- {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",
- }) =>
-
-
- {label}
-
-
- {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",
+ }) =>
+
+
+ {label}
+
+
+ {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 zero(axis)}>
- {t("zero {{axis}}", { axis })}
- ;
-}
-
-export function ZeroRow({ botDisconnected }: ZeroRowProps) {
- return
-
-
- {t("SET ZERO POSITION")}
-
-
-
- {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 = () =>
{t("Binding")}
-
-
-
- {t("target")}
-
+
;
-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))}
({
controlPanelState: {
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
@@ -116,7 +118,10 @@ export const botReducer = generateReducer(initialState())
.add(Actions.BULK_TOGGLE_CONTROL_PANEL, (s, a) => {
s.controlPanelState.homing_and_calibration = a.payload;
s.controlPanelState.motors = a.payload;
- s.controlPanelState.encoders_and_endstops = a.payload;
+ s.controlPanelState.encoders = a.payload;
+ s.controlPanelState.endstops = a.payload;
+ s.controlPanelState.error_handling = a.payload;
+ s.controlPanelState.pin_bindings = a.payload;
s.controlPanelState.pin_guard = a.payload;
s.controlPanelState.danger_zone = a.payload;
return s;
@@ -199,8 +204,7 @@ function legacyStatusHandler(state: BotState,
const nextSyncStatus = maybeNegateStatus(info);
- versionOK(informational_settings.controller_version,
- EXPECTED_MAJOR, EXPECTED_MINOR);
+ versionOK(informational_settings.controller_version);
state.hardware.informational_settings.sync_status = nextSyncStatus;
return state;
}
diff --git a/frontend/external_urls.ts b/frontend/external_urls.ts
new file mode 100644
index 000000000..47e123e98
--- /dev/null
+++ b/frontend/external_urls.ts
@@ -0,0 +1,51 @@
+enum Org {
+ FarmBot = "FarmBot",
+ FarmBotLabs = "FarmBot-Labs",
+}
+
+export enum FarmBotRepo {
+ FarmBotWebApp = "Farmbot-Web-App",
+ FarmBotOS = "farmbot_os",
+ FarmBotArduinoFirmware = "farmbot-arduino-firmware",
+}
+
+enum FbosFile {
+ featureMinVersions = "FEATURE_MIN_VERSIONS.json",
+ osReleaseNotes = "RELEASE_NOTES.md",
+}
+
+export namespace ExternalUrl {
+ const GITHUB = "https://github.com";
+ const GITHUB_RAW = "https://raw.githubusercontent.com";
+ const GITHUB_API = "https://api.github.com";
+ const OPENFARM = "https://openfarm.cc";
+ const SOFTWARE_DOCS = "https://software.farm.bot";
+ const FORUM = "http://forum.farmbot.org";
+ const SHOPIFY_CDN = "https://cdn.shopify.com/s/files/1/2040/0289/files";
+
+ const FBOS_RAW = `${GITHUB_RAW}/${Org.FarmBot}/${FarmBotRepo.FarmBotOS}`;
+ export const featureMinVersions = `${FBOS_RAW}/${FbosFile.featureMinVersions}`;
+ export const osReleaseNotes = `${FBOS_RAW}/${FbosFile.osReleaseNotes}`;
+
+ export const latestRelease =
+ `${GITHUB_API}/repos/${Org.FarmBot}/${FarmBotRepo.FarmBotOS}/releases/latest`;
+
+ export const gitHubFarmBot = `${GITHUB}/${Org.FarmBot}`;
+ export const webAppRepo =
+ `${GITHUB}/${Org.FarmBot}/${FarmBotRepo.FarmBotWebApp}`;
+
+ export const softwareDocs = `${SOFTWARE_DOCS}/docs`;
+ export const softwareForum = `${FORUM}/c/software`;
+
+ export namespace OpenFarm {
+ export const cropApi = `${OPENFARM}/api/v1/crops/`;
+ export const cropBrowse = `${OPENFARM}/crops/`;
+ export const newCrop = `${OPENFARM}/en/crops/new`;
+ }
+
+ export namespace Videos {
+ export const desktop =
+ `${SHOPIFY_CDN}/Farm_Designer_Loop.mp4?9552037556691879018`;
+ export const mobile = `${SHOPIFY_CDN}/Controls.png?9668345515035078097`;
+ }
+}
diff --git a/frontend/farm_designer/map/layers/farmbot/farmbot_layer.tsx b/frontend/farm_designer/map/layers/farmbot/farmbot_layer.tsx
index 087b919b1..96df099ea 100644
--- a/frontend/farm_designer/map/layers/farmbot/farmbot_layer.tsx
+++ b/frontend/farm_designer/map/layers/farmbot/farmbot_layer.tsx
@@ -8,7 +8,7 @@ export function FarmBotLayer(props: FarmBotLayerProps) {
visible, stopAtHome, botSize, plantAreaOffset, mapTransformProps,
peripherals, eStopStatus, botLocationData, getConfigValue
} = props;
- return visible ?
+ return visible ?
", () => {
const wrapper = svgMount( );
expect(wrapper.find("use").props().transform).toEqual(expected);
});
+
+ it("handles bad data", () => {
+ const p = fakeProps();
+ p.pulloutDirection = 1.1;
+ p.quadrant = 1.1;
+ const wrapper = svgMount( );
+ expect(wrapper.find("use").props().transform).toEqual("rotate(0, 10, 20)");
+ });
});
describe(" ", () => {
diff --git a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_label_test.ts b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_label_test.ts
index 130c77f37..41323925d 100644
--- a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_label_test.ts
+++ b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_label_test.ts
@@ -49,4 +49,8 @@ describe("textAnchorPosition()", () => {
expect(textAnchorPosition(4, 3, true)).toEqual(END);
expect(textAnchorPosition(4, 4, true)).toEqual(START);
});
+
+ it("handles bad data", () => {
+ expect(textAnchorPosition(1.1, 1.1, false)).toEqual(START);
+ });
});
diff --git a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_layer_test.tsx b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_layer_test.tsx
index 4a06683a0..5788588ad 100644
--- a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_layer_test.tsx
+++ b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_layer_test.tsx
@@ -4,6 +4,11 @@ jest.mock("../../../../../history", () => ({
getPathArray: jest.fn(() => { return mockPath.split("/"); })
}));
+let mockDev = false;
+jest.mock("../../../../../account/dev/dev_support", () => ({
+ DevSettings: { futureFeaturesEnabled: () => mockDev }
+}));
+
import * as React from "react";
import { ToolSlotLayer, ToolSlotLayerProps } from "../tool_slot_layer";
import {
@@ -53,6 +58,7 @@ describe(" ", () => {
});
it("navigates to tools page", async () => {
+ mockDev = true;
mockPath = "/app/designer/plants";
const p = fakeProps();
const wrapper = shallow( );
diff --git a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx
index 1f203c2a1..75451aefc 100644
--- a/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx
+++ b/frontend/farm_designer/map/layers/tool_slots/__tests__/tool_slot_point_test.tsx
@@ -48,10 +48,10 @@ describe(" ", () => {
const p = fakeProps();
p.slot.toolSlot.body.id = 1;
const wrapper = svgMount( );
- mockDev = false;
+ mockDev = true;
wrapper.find("g").first().simulate("click");
expect(history.push).not.toHaveBeenCalled();
- mockDev = true;
+ mockDev = false;
wrapper.find("g").first().simulate("click");
expect(history.push).toHaveBeenCalledWith("/app/designer/tool-slots/1");
});
diff --git a/frontend/farm_designer/map/layers/tool_slots/tool_graphics.tsx b/frontend/farm_designer/map/layers/tool_slots/tool_graphics.tsx
index b9a6bb6de..b87288589 100644
--- a/frontend/farm_designer/map/layers/tool_slots/tool_graphics.tsx
+++ b/frontend/farm_designer/map/layers/tool_slots/tool_graphics.tsx
@@ -180,23 +180,30 @@ export interface GantryToolSlotGraphicProps {
xySwap: boolean;
}
+/** dimensions */
+enum Trough {
+ width = 20,
+ length = 45,
+ wall = 4,
+}
+
export const GantryToolSlot = (props: GantryToolSlotGraphicProps) => {
const { x, y, xySwap } = props;
- const slotLengthX = xySwap ? 24 : 49;
- const slotLengthY = xySwap ? 49 : 24;
+ const slotLengthX = Trough.wall + (xySwap ? Trough.width : Trough.length);
+ const slotLengthY = Trough.wall + (xySwap ? Trough.length : Trough.width);
return
;
};
const SeedTrough = (props: ToolGraphicProps) => {
const { x, y, hovered, dispatch, uuid, xySwap } = props;
- const slotLengthX = xySwap ? 20 : 45;
- const slotLengthY = xySwap ? 45 : 20;
+ const slotLengthX = xySwap ? Trough.width : Trough.length;
+ const slotLengthY = xySwap ? Trough.length : Trough.width;
return dispatch(setToolHover(uuid))}
onMouseLeave={() => dispatch(setToolHover(undefined))}>
diff --git a/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx b/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx
index 9b12293c8..18d9ff713 100644
--- a/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx
+++ b/frontend/farm_designer/map/layers/tool_slots/tool_label.tsx
@@ -39,7 +39,7 @@ export const textAnchorPosition = (
case Anchor.end: return { anchor: "end", x: -40, y: 10 };
case Anchor.middleTop: return { anchor: "middle", x: 0, y: 60 };
case Anchor.middleBottom: return { anchor: "middle", x: 0, y: -40 };
- default: return { anchor: "start", x: 40, y: 10 };
+ default: throw new Error("https://xkcd.com/2200");
}
};
diff --git a/frontend/farm_designer/map/layers/tool_slots/tool_slot_layer.tsx b/frontend/farm_designer/map/layers/tool_slots/tool_slot_layer.tsx
index d222e4dae..d03dcef25 100644
--- a/frontend/farm_designer/map/layers/tool_slots/tool_slot_layer.tsx
+++ b/frontend/farm_designer/map/layers/tool_slots/tool_slot_layer.tsx
@@ -19,7 +19,7 @@ export function ToolSlotLayer(props: ToolSlotLayerProps) {
const pathArray = getPathArray();
const canClickTool = !(pathArray[3] === "plants" && pathArray.length > 4);
const goToToolsPage = () => canClickTool &&
- !DevSettings.futureFeaturesEnabled() && history.push("/app/tools");
+ DevSettings.futureFeaturesEnabled() && history.push("/app/tools");
const { slots, visible, mapTransformProps } = props;
const cursor = canClickTool ? "pointer" : "default";
diff --git a/frontend/farm_designer/map/layers/tool_slots/tool_slot_point.tsx b/frontend/farm_designer/map/layers/tool_slots/tool_slot_point.tsx
index d63554c8c..ddb889b00 100644
--- a/frontend/farm_designer/map/layers/tool_slots/tool_slot_point.tsx
+++ b/frontend/farm_designer/map/layers/tool_slots/tool_slot_point.tsx
@@ -43,7 +43,7 @@ export const ToolSlotPoint = (props: TSPProps) => {
xySwap,
};
return DevSettings.futureFeaturesEnabled() &&
+ onClick={() => !DevSettings.futureFeaturesEnabled() &&
history.push(`/app/designer/tool-slots/${id}`)}>
{pullout_direction &&
- {DevSettings.futureFeaturesEnabled() &&
+ {!DevSettings.futureFeaturesEnabled() &&
{t("Edit on")}
-
{"OpenFarm"}
diff --git a/frontend/farm_designer/plants/grid/__tests__/plant_grid_test.tsx b/frontend/farm_designer/plants/grid/__tests__/plant_grid_test.tsx
index 297a3c3bf..33ce512ae 100644
--- a/frontend/farm_designer/plants/grid/__tests__/plant_grid_test.tsx
+++ b/frontend/farm_designer/plants/grid/__tests__/plant_grid_test.tsx
@@ -7,7 +7,7 @@ import * as React from "react";
import { mount } from "enzyme";
import { PlantGrid } from "../plant_grid";
import { saveGrid, stashGrid } from "../thunks";
-import { error } from "../../../../toast/toast";
+import { error, success } from "../../../../toast/toast";
describe("PlantGrid", () => {
function fakeProps() {
@@ -39,8 +39,11 @@ describe("PlantGrid", () => {
it("saves a grid", async () => {
const props = fakeProps();
const pg = mount
( ).instance();
+ const oldId = pg.state.gridId;
await pg.saveGrid();
- expect(saveGrid).toHaveBeenCalledWith(pg.state.gridId);
+ expect(saveGrid).toHaveBeenCalledWith(oldId);
+ expect(success).toHaveBeenCalledWith("16 plants added.");
+ expect(pg.state.gridId).not.toEqual(oldId);
});
it("stashes a grid", async () => {
diff --git a/frontend/farm_designer/plants/grid/plant_grid.tsx b/frontend/farm_designer/plants/grid/plant_grid.tsx
index 6878640a7..9d83b41f3 100644
--- a/frontend/farm_designer/plants/grid/plant_grid.tsx
+++ b/frontend/farm_designer/plants/grid/plant_grid.tsx
@@ -9,13 +9,18 @@ import { initPlantGrid } from "./generate_grid";
import { init } from "../../../api/crud";
import { uuid } from "farmbot";
import { saveGrid, stashGrid } from "./thunks";
-import { error } from "../../../toast/toast";
+import { error, success } from "../../../toast/toast";
import { t } from "../../../i18next_wrapper";
import { GridInput } from "./grid_input";
export class PlantGrid extends React.Component {
state: PlantGridState = { ...EMPTY_PLANT_GRID, gridId: uuid() };
+ get plantCount() {
+ const { numPlantsH, numPlantsV } = this.state.grid;
+ return numPlantsH * numPlantsV;
+ }
+
onchange = (key: PlantGridKey, val: number) => {
const grid = { ...this.state.grid, [key]: val };
this.setState({ grid });
@@ -33,9 +38,7 @@ export class PlantGrid extends React.Component {
}
performPreview = () => {
- const { numPlantsH, numPlantsV } = this.state.grid;
- const total = numPlantsH * numPlantsV;
- if (total > 100) {
+ if (this.plantCount > 100) {
error(t("Please make a grid with less than 100 plants"));
return;
}
@@ -57,7 +60,10 @@ export class PlantGrid extends React.Component {
saveGrid = () => {
const p: Promise<{}> = this.props.dispatch(saveGrid(this.state.gridId));
- return p.then(() => this.setState(EMPTY_PLANT_GRID));
+ return p.then(() => {
+ success(t("{{ count }} plants added.", { count: this.plantCount }));
+ this.setState({ ...EMPTY_PLANT_GRID, gridId: uuid() });
+ });
}
inputs = () => {
@@ -73,16 +79,16 @@ export class PlantGrid extends React.Component {
case "clean":
return ;
case "dirty":
return ;
}
diff --git a/frontend/farm_designer/plants/openfarm_search_results.tsx b/frontend/farm_designer/plants/openfarm_search_results.tsx
index 2efddc935..d6040a26f 100644
--- a/frontend/farm_designer/plants/openfarm_search_results.tsx
+++ b/frontend/farm_designer/plants/openfarm_search_results.tsx
@@ -5,6 +5,7 @@ import {
} from "../../ui/empty_state_wrapper";
import { Content } from "../../constants";
import { t } from "../../i18next_wrapper";
+import { ExternalUrl } from "../../external_urls";
/** A stripped down version of OFSearchResult */
interface Result {
@@ -24,7 +25,7 @@ export class OpenFarmResults extends React.Component {
get text(): JSX.Element {
return {`${t(Content.CROP_NOT_FOUND_INTRO)} `}
-
+
{t(Content.CROP_NOT_FOUND_LINK)}
;
diff --git a/frontend/farm_designer/point_groups/__tests__/group_detail_active_test.tsx b/frontend/farm_designer/point_groups/__tests__/group_detail_active_test.tsx
index 19ffd8c27..2d7909c5b 100644
--- a/frontend/farm_designer/point_groups/__tests__/group_detail_active_test.tsx
+++ b/frontend/farm_designer/point_groups/__tests__/group_detail_active_test.tsx
@@ -24,6 +24,7 @@ import {
import { save, edit } from "../../../api/crud";
import { SpecialStatus } from "farmbot";
import { DEFAULT_CRITERIA } from "../criteria/interfaces";
+import { Content } from "../../../constants";
describe(" ", () => {
const fakeProps = (): GroupDetailActiveProps => {
@@ -105,16 +106,23 @@ describe(" ", () => {
});
it("shows paths", () => {
- mockDev = true;
- const p = fakeProps();
- const wrapper = mount( );
- expect(wrapper.text().toLowerCase()).toContain("optimized");
- });
-
- it("doesn't show paths", () => {
mockDev = false;
const p = fakeProps();
const wrapper = mount( );
- expect(wrapper.text().toLowerCase()).not.toContain("optimized");
+ expect(wrapper.text().toLowerCase()).toContain("0m");
+ });
+
+ it("doesn't show paths", () => {
+ mockDev = true;
+ const p = fakeProps();
+ const wrapper = mount( );
+ expect(wrapper.text().toLowerCase()).not.toContain("0m");
+ });
+
+ it("shows random warning text", () => {
+ const p = fakeProps();
+ p.group.body.sort_type = "random";
+ const wrapper = mount( );
+ expect(wrapper.text()).toContain(Content.SORT_DESCRIPTION);
});
});
diff --git a/frontend/farm_designer/point_groups/__tests__/paths_test.tsx b/frontend/farm_designer/point_groups/__tests__/paths_test.tsx
index ffa057ad0..31cecd0ba 100644
--- a/frontend/farm_designer/point_groups/__tests__/paths_test.tsx
+++ b/frontend/farm_designer/point_groups/__tests__/paths_test.tsx
@@ -1,5 +1,12 @@
jest.mock("../../../api/crud", () => ({ edit: jest.fn() }));
+let mockDev = false;
+jest.mock("../../../account/dev/dev_support", () => ({
+ DevSettings: {
+ futureFeaturesEnabled: () => mockDev,
+ }
+}));
+
import * as React from "react";
import { shallow, mount } from "enzyme";
import {
@@ -141,6 +148,7 @@ describe(" ", () => {
p.pathPoints = cases.order.xy_ascending;
const wrapper = mount( );
expect(wrapper.state().pathData).toEqual(cases.distance);
+ expect(wrapper.text().toLowerCase()).not.toContain("optimized");
});
it.each<[PointGroupSortType]>([
@@ -154,4 +162,24 @@ describe(" ", () => {
expect(SORT_OPTIONS[sortType](cases.order.xy_ascending))
.toEqual(cases.order[sortType]);
});
+
+ it("renders new sort type", () => {
+ mockDev = true;
+ const p = fakeProps();
+ const cases = pathTestCases();
+ p.pathPoints = cases.order.xy_ascending;
+ const wrapper = mount( );
+ expect(wrapper.text().toLowerCase()).toContain("optimized");
+ });
+
+ it("doesn't generate data twice", () => {
+ const p = fakeProps();
+ const cases = pathTestCases();
+ p.pathPoints = cases.order.xy_ascending;
+ const wrapper = mount( );
+ expect(wrapper.state().pathData).toEqual(cases.distance);
+ wrapper.setState({ pathData: { nn: 0 } });
+ wrapper.update();
+ expect(wrapper.state().pathData).toEqual({ nn: 0 });
+ });
});
diff --git a/frontend/farm_designer/point_groups/__tests__/point_group_item_test.tsx b/frontend/farm_designer/point_groups/__tests__/point_group_item_test.tsx
index f87d6cb79..90eba3063 100644
--- a/frontend/farm_designer/point_groups/__tests__/point_group_item_test.tsx
+++ b/frontend/farm_designer/point_groups/__tests__/point_group_item_test.tsx
@@ -45,7 +45,6 @@ describe(" ", () => {
const p = fakeProps();
p.point = fakePlant();
const i = new PointGroupItem(p);
- i.setState = jest.fn();
const fakeImgEvent = imgEvent();
await i.maybeGetCachedIcon(fakeImgEvent);
const slug = i.props.point.body.pointer_type === "Plant" ?
@@ -55,11 +54,17 @@ describe(" ", () => {
expect(setImgSrc).not.toHaveBeenCalled();
});
+ it("sets icon in state", () => {
+ const i = new PointGroupItem(fakeProps());
+ i.setState = jest.fn();
+ i.setIconState("fake icon");
+ expect(i.setState).toHaveBeenCalledWith({ icon: "fake icon" });
+ });
+
it("fetches point icon", () => {
const p = fakeProps();
p.point = fakePoint();
const i = new PointGroupItem(p);
- i.setState = jest.fn();
const fakeImgEvent = imgEvent();
i.maybeGetCachedIcon(fakeImgEvent);
expect(maybeGetCachedPlantIcon).not.toHaveBeenCalled();
@@ -71,7 +76,6 @@ describe(" ", () => {
const p = fakeProps();
p.point = fakeToolSlot();
const i = new PointGroupItem(p);
- i.setState = jest.fn();
const fakeImgEvent = imgEvent();
i.maybeGetCachedIcon(fakeImgEvent);
expect(maybeGetCachedPlantIcon).not.toHaveBeenCalled();
diff --git a/frontend/farm_designer/point_groups/__tests__/point_group_sort_selector_test.tsx b/frontend/farm_designer/point_groups/__tests__/point_group_sort_selector_test.tsx
index 9c761fe55..bfd1f1d60 100644
--- a/frontend/farm_designer/point_groups/__tests__/point_group_sort_selector_test.tsx
+++ b/frontend/farm_designer/point_groups/__tests__/point_group_sort_selector_test.tsx
@@ -1,14 +1,10 @@
-import * as React from "react";
import {
- isSortType, sortTypeChange, SORT_OPTIONS, PointGroupSortSelector,
- PointGroupSortSelectorProps
+ isSortType, sortTypeChange, SORT_OPTIONS
} from "../point_group_sort_selector";
import { DropDownItem } from "../../../ui";
import { PointGroupSortType } from "farmbot/dist/resources/api_resources";
import { TaggedPoint } from "farmbot";
import { fakePlant } from "../../../__test_support__/fake_state/resources";
-import { mount } from "enzyme";
-import { Content } from "../../../constants";
const tests: [string, boolean][] = [
["", false],
@@ -89,15 +85,3 @@ describe("sort()", () => {
expect(results).toEqual(["C", "D", "B", "A"]);
});
});
-
-describe(" ", () => {
- const fakeProps = (): PointGroupSortSelectorProps => ({
- onChange: jest.fn(),
- value: "random",
- });
-
- it("shows random warning text", () => {
- const wrapper = mount( );
- expect(wrapper.text()).toContain(Content.SORT_DESCRIPTION);
- });
-});
diff --git a/frontend/farm_designer/point_groups/group_detail_active.tsx b/frontend/farm_designer/point_groups/group_detail_active.tsx
index b7b9251c4..b56ed3e71 100644
--- a/frontend/farm_designer/point_groups/group_detail_active.tsx
+++ b/frontend/farm_designer/point_groups/group_detail_active.tsx
@@ -97,9 +97,24 @@ export class GroupDetailActive
defaultValue={group.body.name}
onChange={this.update}
onBlur={this.saveGroup} />
-
+
+
+ {t("SORT BY")}
+
+ {!DevSettings.futureFeaturesEnabled()
+ ?
p.body.id))}
+ pathPoints={this.pointsSelectedByGroup}
+ dispatch={dispatch}
+ group={group} />
+ : }
+
+ {group.body.sort_type == "random" && t(Content.SORT_DESCRIPTION)}
+
+
{t("GROUP MEMBERS ({{count}})", { count: this.icons.length })}
@@ -117,11 +132,6 @@ export class GroupDetailActive
{this.props.shouldDisplay(Feature.criteria_groups) &&
}
- {DevSettings.futureFeaturesEnabled() &&
- }
({ x: point.body.x, y: point.body.y });
@@ -66,7 +67,8 @@ export const PathInfoBar = (props: PathInfoBarProps) => {
const normalizedLength = pathLength / maxLength * 100;
const sortLabel =
sortTypeKey == "nn" ? "Optimized" : sortOptionsTable()[sortTypeKey];
- return
dispatch({ type: Actions.TRY_SORT_TYPE, payload: sortTypeKey })}
onMouseLeave={() =>
@@ -74,9 +76,11 @@ export const PathInfoBar = (props: PathInfoBarProps) => {
onClick={() =>
sortTypeKey == "nn"
? error(t("Not supported yet."))
- : dispatch(edit(group, { sort_type: sortTypeKey }))}
- style={{ width: `${normalizedLength}%` }}>
- {`${sortLabel}: ${Math.round(pathLength / 10) / 100}m`}
+ : dispatch(edit(group, { sort_type: sortTypeKey }))}>
+
+ {`${sortLabel}: ${Math.round(pathLength / 10) / 100}m`}
+
;
};
@@ -101,15 +105,17 @@ export class Paths extends React.Component {
};
render() {
- if (!this.state.pathData.nn) { this.generatePathData(this.props.pathPoints); }
- return
-
{t("Path lengths by sort type")}
- {SORT_TYPES.concat("nn").map(st =>
-
)}
+ if (!isNumber(this.state.pathData.nn)) {
+ this.generatePathData(this.props.pathPoints);
+ }
+ return
+ {SORT_TYPES.concat(DevSettings.futureFeaturesEnabled() ? "nn" : [])
+ .map(sortType =>
+
)}
;
}
}
diff --git a/frontend/farm_designer/point_groups/point_group_item.tsx b/frontend/farm_designer/point_groups/point_group_item.tsx
index 8d6d7b101..cee43c163 100644
--- a/frontend/farm_designer/point_groups/point_group_item.tsx
+++ b/frontend/farm_designer/point_groups/point_group_item.tsx
@@ -57,6 +57,8 @@ export class PointGroupItem
this.leave();
}
+ setIconState = (icon: string) => this.setState({ icon });
+
get criteriaIcon() {
return !this.props.group.body.point_ids
.includes(this.props.point.body.id || 0);
@@ -67,7 +69,7 @@ export class PointGroupItem
switch (this.props.point.body.pointer_type) {
case "Plant":
const slug = this.props.point.body.openfarm_slug;
- maybeGetCachedPlantIcon(slug, img, icon => this.setState({ icon }));
+ maybeGetCachedPlantIcon(slug, img, this.setIconState);
break;
case "GenericPointer":
const { color } = this.props.point.body.meta;
diff --git a/frontend/farm_designer/point_groups/point_group_sort_selector.tsx b/frontend/farm_designer/point_groups/point_group_sort_selector.tsx
index 5c1d6a586..e1c335dd9 100644
--- a/frontend/farm_designer/point_groups/point_group_sort_selector.tsx
+++ b/frontend/farm_designer/point_groups/point_group_sort_selector.tsx
@@ -3,7 +3,6 @@ import { PointGroupSortType } from "farmbot/dist/resources/api_resources";
import { FBSelect, DropDownItem } from "../../ui";
import { t } from "../../i18next_wrapper";
import { shuffle, sortBy } from "lodash";
-import { Content } from "../../constants";
import { TaggedPoint } from "farmbot";
export interface PointGroupSortSelectorProps {
@@ -42,22 +41,11 @@ export const sortTypeChange = (cb: Function) => (ddi: DropDownItem) => {
};
export function PointGroupSortSelector(p: PointGroupSortSelectorProps) {
-
- return
-
-
- {t("SORT BY")}
-
-
-
-
- {(p.value == "random") ? t(Content.SORT_DESCRIPTION) : ""}
-
-
;
+ return
;
}
type Sorter = (p: TaggedPoint[]) => TaggedPoint[];
diff --git a/frontend/farm_designer/tools/__tests__/add_tool_test.tsx b/frontend/farm_designer/tools/__tests__/add_tool_test.tsx
index 3f6e93f7c..e982afc55 100644
--- a/frontend/farm_designer/tools/__tests__/add_tool_test.tsx
+++ b/frontend/farm_designer/tools/__tests__/add_tool_test.tsx
@@ -11,6 +11,7 @@ import { fakeState } from "../../../__test_support__/fake_state";
import { SaveBtn } from "../../../ui";
import { initSave } from "../../../api/crud";
import { history } from "../../../history";
+import { error } from "../../../toast/toast";
describe("
", () => {
const fakeProps = (): AddToolProps => ({
@@ -37,10 +38,19 @@ describe("
", () => {
expect(initSave).toHaveBeenCalledWith("Tool", { name: "Foo" });
});
- it("adds stock tools", () => {
+ it("doesn't add stock tools", () => {
const wrapper = mount(
);
wrapper.find("button").last().simulate("click");
- expect(initSave).toHaveBeenCalledTimes(6);
+ expect(error).toHaveBeenCalledWith("Please choose a FarmBot model.");
+ expect(initSave).not.toHaveBeenCalledTimes(6);
+ expect(history.push).not.toHaveBeenCalledWith("/app/designer/tools");
+ });
+
+ it("adds stock tools", () => {
+ const wrapper = mount(
);
+ wrapper.setState({ model: "express" });
+ wrapper.find("button").last().simulate("click");
+ expect(initSave).toHaveBeenCalledTimes(2);
expect(history.push).toHaveBeenCalledWith("/app/designer/tools");
});
});
diff --git a/frontend/farm_designer/tools/add_tool.tsx b/frontend/farm_designer/tools/add_tool.tsx
index b95508245..2eba033c9 100644
--- a/frontend/farm_designer/tools/add_tool.tsx
+++ b/frontend/farm_designer/tools/add_tool.tsx
@@ -5,11 +5,20 @@ import {
} from "../designer_panel";
import { Everything } from "../../interfaces";
import { t } from "../../i18next_wrapper";
-import { SaveBtn } from "../../ui";
+import { SaveBtn, FBSelect, DropDownItem } from "../../ui";
import { SpecialStatus } from "farmbot";
import { initSave } from "../../api/crud";
import { Panel } from "../panel_header";
import { history } from "../../history";
+import { error } from "../../toast/toast";
+
+enum Model { genesis14 = "genesis14", genesis15 = "genesis15", express = "express" }
+
+const MODEL_DDI_LOOKUP = (): { [x: string]: DropDownItem } => ({
+ [Model.genesis14]: { label: t("Genesis v1.2-v1.4"), value: Model.genesis14 },
+ [Model.genesis15]: { label: t("Genesis v1.5+"), value: Model.genesis15 },
+ [Model.express]: { label: t("Express"), value: Model.express },
+});
export interface AddToolProps {
dispatch: Function;
@@ -17,6 +26,7 @@ export interface AddToolProps {
export interface AddToolState {
toolName: string;
+ model: Model | undefined;
}
export const mapStateToProps = (props: Everything): AddToolProps => ({
@@ -24,7 +34,7 @@ export const mapStateToProps = (props: Everything): AddToolProps => ({
});
export class RawAddTool extends React.Component
{
- state: AddToolState = { toolName: "" };
+ state: AddToolState = { toolName: "", model: undefined };
newTool = (name: string) => {
this.props.dispatch(initSave("Tool", { name }));
@@ -35,28 +45,60 @@ export class RawAddTool extends React.Component {
history.push("/app/designer/tools");
}
- get stockToolNames() {
- return [
- t("Seeder"),
- t("Watering Nozzle"),
- t("Weeder"),
- t("Soil Sensor"),
- t("Seed Bin"),
- t("Seed Tray"),
- ];
+ stockToolNames = (model: Model) => {
+ switch (model) {
+ case Model.genesis14:
+ return [
+ t("Seeder"),
+ t("Watering Nozzle"),
+ t("Weeder"),
+ t("Soil Sensor"),
+ t("Seed Bin"),
+ t("Seed Tray"),
+ ];
+ case Model.genesis15:
+ return [
+ t("Seeder"),
+ t("Watering Nozzle"),
+ t("Weeder"),
+ t("Soil Sensor"),
+ t("Seed Bin"),
+ t("Seed Tray"),
+ t("Seed Trough 1"),
+ t("Seed Trough 2"),
+ ];
+ case Model.express:
+ return [
+ t("Seed Trough 1"),
+ t("Seed Trough 2"),
+ ];
+ }
}
AddStockTools = () =>
{t("Add stock tools")}
-
- {this.stockToolNames.map(n => {n} )}
-
+
this.setState({ model: ddi.value as Model })}
+ />
+ {this.state.model &&
+
+ {this.stockToolNames(this.state.model).map(n => {n} )}
+ }
{
- this.stockToolNames.map(n => this.newTool(n));
- history.push("/app/designer/tools");
+ if (this.state.model) {
+ this.stockToolNames(this.state.model).map(n => this.newTool(n));
+ history.push("/app/designer/tools");
+ } else {
+ error(t("Please choose a FarmBot model."));
+ }
}}>
{t("Stock Tools")}
diff --git a/frontend/farm_designer/tools/index.tsx b/frontend/farm_designer/tools/index.tsx
index ca21b2e32..bcfc3ccae 100644
--- a/frontend/farm_designer/tools/index.tsx
+++ b/frontend/farm_designer/tools/index.tsx
@@ -159,14 +159,20 @@ export class RawTools extends React.Component {
getToolName={this.getToolName} />)}
- InactiveTools = () =>
-
-
{t("inactive tools")}
+ Tools = () =>
+
+
+
{t("tools")}
+
+
+
+
+
+
{this.props.tools
.filter(tool => !tool.body.name ||
tool.body.name && tool.body.name.toLowerCase()
.includes(this.state.searchTerm.toLowerCase()))
- .filter(tool => tool.body.status === "inactive")
.map(tool =>
{
render() {
const panelName = "tools";
+ const hasTools = this.props.tools.length > 0;
return
+ linkTo={!hasTools ? "/app/designer/tools/add" : undefined}
+ title={!hasTools ? t("Add tool") : undefined}>
0}
+ notEmpty={hasTools}
graphic={EmptyStateGraphic.tools}
title={t("Add a tool")}
text={Content.NO_TOOLS}
colorScheme={"tools"}>
-
+
;
diff --git a/frontend/farm_designer/util.ts b/frontend/farm_designer/util.ts
index 40500c821..425fece84 100644
--- a/frontend/farm_designer/util.ts
+++ b/frontend/farm_designer/util.ts
@@ -4,8 +4,10 @@ import { DEFAULT_ICON } from "../open_farm/icons";
import { Actions } from "../constants";
import { ExecutableType } from "farmbot/dist/resources/api_resources";
import { get } from "lodash";
+import { ExternalUrl } from "../external_urls";
-const url = (q: string) => `${OpenFarm.cropUrl}?include=pictures&filter=${q}`;
+const url = (q: string) =>
+ `${ExternalUrl.OpenFarm.cropApi}?include=pictures&filter=${q}`;
const openFarmSearchQuery = (q: string): AxiosPromise =>
axios.get(url(q));
diff --git a/frontend/farmware/__tests__/actions_test.ts b/frontend/farmware/__tests__/actions_test.ts
index a32929d45..d4bfde0b4 100644
--- a/frontend/farmware/__tests__/actions_test.ts
+++ b/frontend/farmware/__tests__/actions_test.ts
@@ -2,8 +2,8 @@ jest.mock("axios", () => ({
get: jest.fn(() => {
return Promise.resolve({
data: [
- { manifest: "url", name: "farmware0" },
- { manifest: "url", name: "farmware1" }
+ { package: "farmware0" },
+ { package: "farmware1" }
]
});
}),
diff --git a/frontend/farmware/actions.ts b/frontend/farmware/actions.ts
index 0453e9d7f..c10135119 100644
--- a/frontend/farmware/actions.ts
+++ b/frontend/farmware/actions.ts
@@ -1,17 +1,14 @@
import axios from "axios";
-import { FarmwareManifestEntry } from "./interfaces";
import { Actions } from "../constants";
import { urlFor } from "../api/crud";
-
-const farmwareManifestUrl =
- "https://raw.githubusercontent.com/FarmBot-Labs/farmware_manifests" +
- "/master/manifest.json";
+import { API } from "../api";
+import { FarmwareManifest } from "farmbot";
export const getFirstPartyFarmwareList = () => {
return (dispatch: Function) => {
- axios.get(farmwareManifestUrl)
+ axios.get(API.current.firstPartyFarmwarePath)
.then(r => {
- const names = r.data.map((fw: FarmwareManifestEntry) => fw.name);
+ const names = r.data.map(fw => fw.package);
dispatch({
type: Actions.FETCH_FIRST_PARTY_FARMWARE_NAMES_OK,
payload: names
diff --git a/frontend/farmware/camera_calibration/__tests__/camera_calibration_test.tsx b/frontend/farmware/camera_calibration/__tests__/camera_calibration_test.tsx
index 577f65133..afdfe119e 100644
--- a/frontend/farmware/camera_calibration/__tests__/camera_calibration_test.tsx
+++ b/frontend/farmware/camera_calibration/__tests__/camera_calibration_test.tsx
@@ -3,6 +3,13 @@ jest.mock("../../../device", () => ({ getDevice: () => mockDevice }));
jest.mock("../actions", () => ({ scanImage: jest.fn() }));
jest.mock("../../images/actions", () => ({ selectImage: jest.fn() }));
+let mockDev = false;
+jest.mock("../../../account/dev/dev_support", () => ({
+ DevSettings: {
+ futureFeaturesEnabled: () => mockDev,
+ }
+}));
+
import * as React from "react";
import { mount, shallow } from "enzyme";
import { CameraCalibration } from "../camera_calibration";
@@ -12,6 +19,7 @@ import { selectImage } from "../../images/actions";
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
import { error } from "../../../toast/toast";
import { Content, ToolTips } from "../../../constants";
+import { SPECIAL_VALUES } from "../../weed_detector/remote_env/constants";
describe(" ", () => {
const fakeProps = (): CameraCalibrationProps => ({
@@ -116,4 +124,21 @@ describe(" ", () => {
expect(error).toHaveBeenCalledWith(
ToolTips.SELECT_A_CAMERA, Content.NO_CAMERA_SELECTED);
});
+
+ it("toggles simple version", () => {
+ mockDev = true;
+ const p = fakeProps();
+ const wrapper = mount( );
+ wrapper.find("input").first().simulate("change");
+ expect(mockDevice.setUserEnv).toHaveBeenCalledWith({
+ CAMERA_CALIBRATION_easy_calibration: "\"FALSE\""
+ });
+ });
+
+ it("renders simple version", () => {
+ const p = fakeProps();
+ p.wDEnv = { CAMERA_CALIBRATION_easy_calibration: SPECIAL_VALUES.TRUE };
+ const wrapper = mount( );
+ expect(wrapper.text().toLowerCase()).not.toContain("blur");
+ });
});
diff --git a/frontend/farmware/camera_calibration/camera_calibration.tsx b/frontend/farmware/camera_calibration/camera_calibration.tsx
index 6654eac51..515a7066c 100644
--- a/frontend/farmware/camera_calibration/camera_calibration.tsx
+++ b/frontend/farmware/camera_calibration/camera_calibration.tsx
@@ -8,7 +8,7 @@ import { selectImage } from "../images/actions";
import { calibrate, scanImage } from "./actions";
import { envGet } from "../weed_detector/remote_env/selectors";
import { MustBeOnline, isBotOnline } from "../../devices/must_be_online";
-import { WeedDetectorConfig } from "../weed_detector/config";
+import { WeedDetectorConfig, BoolConfig } from "../weed_detector/config";
import { Feature } from "../../devices/interfaces";
import { namespace } from "../weed_detector";
import { t } from "../../i18next_wrapper";
@@ -16,6 +16,10 @@ import { formatEnvKey } from "../weed_detector/remote_env/translators";
import {
cameraBtnProps
} from "../../devices/components/fbos_settings/camera_selection";
+import { ImageFlipper } from "../images/image_flipper";
+import { PhotoFooter } from "../images/photos";
+import { UUID } from "../../resources/interfaces";
+import { DevSettings } from "../../account/dev/dev_support";
export class CameraCalibration extends
React.Component {
@@ -31,9 +35,11 @@ export class CameraCalibration extends
key, JSON.stringify(formatEnvKey(key, value))))
: envSave(key, value)
+ onFlip = (uuid: UUID) => this.props.dispatch(selectImage(uuid));
+
render() {
const camDisabled = cameraBtnProps(this.props.env);
- return
+ return
-
- }
+ {!!envGet(this.namespace("easy_calibration"), this.props.wDEnv)
+ ?
+ : this.props.dispatch(scanImage(id))}
- onFlip={uuid => this.props.dispatch(selectImage(uuid))}
+ onFlip={this.onFlip}
images={this.props.images}
currentImage={this.props.currentImage}
onChange={this.change}
@@ -73,11 +91,10 @@ export class CameraCalibration extends
S_HI={this.props.S_HI}
V_HI={this.props.V_HI}
invertHue={!!envGet(this.namespace("invert_hue_selection"),
- this.props.wDEnv)} />
-
-
+ this.props.wDEnv)} />}
+
;
diff --git a/frontend/farmware/interfaces.ts b/frontend/farmware/interfaces.ts
index 1a9cfe4d1..32d7cb132 100644
--- a/frontend/farmware/interfaces.ts
+++ b/frontend/farmware/interfaces.ts
@@ -24,8 +24,6 @@ export interface FarmwareState {
infoOpen: boolean;
}
-export type FarmwareManifestEntry = Record<"name" | "manifest", string>;
-
export interface FarmwareConfigMenuProps {
show: boolean | undefined;
dispatch: Function;
diff --git a/frontend/farmware/weed_detector/__tests__/config_test.tsx b/frontend/farmware/weed_detector/__tests__/config_test.tsx
index 19495ebfb..1bd300793 100644
--- a/frontend/farmware/weed_detector/__tests__/config_test.tsx
+++ b/frontend/farmware/weed_detector/__tests__/config_test.tsx
@@ -31,18 +31,6 @@ describe("
", () => {
expect(badChange).toThrow("Weed detector got a non-numeric value");
});
- it("changes hue invert value", () => {
- const p = fakeProps();
- const wrapper = shallow(
);
- const input = wrapper.find("input").first();
- input.simulate("change", { currentTarget: { checked: true } });
- expect(p.onChange).toHaveBeenCalledWith(
- "CAMERA_CALIBRATION_invert_hue_selection", 1);
- input.simulate("change", { currentTarget: { checked: false } });
- expect(p.onChange).toHaveBeenCalledWith(
- "CAMERA_CALIBRATION_invert_hue_selection", 0);
- });
-
it("changes number value", () => {
const p = fakeProps();
const wrapper = shallow
( );
diff --git a/frontend/farmware/weed_detector/config.tsx b/frontend/farmware/weed_detector/config.tsx
index 9d775c606..3383c3b32 100644
--- a/frontend/farmware/weed_detector/config.tsx
+++ b/frontend/farmware/weed_detector/config.tsx
@@ -15,6 +15,9 @@ import { isNumber } from "lodash";
import { t } from "../../i18next_wrapper";
export class WeedDetectorConfig extends React.Component {
+ getValue(conf: keyof WD_ENV) { return envGet(conf, this.props.values); }
+ get simple() { return !!this.getValue("CAMERA_CALIBRATION_easy_calibration"); }
+
NumberBox = ({ conf, label }: {
conf: keyof WD_ENV;
label: string;
@@ -25,7 +28,7 @@ export class WeedDetectorConfig extends React.Component {
this.props.onChange(conf, parseFloat(e.currentTarget.value))}
placeholder={label} />
@@ -40,57 +43,48 @@ export class WeedDetectorConfig extends React.Component {
}
};
- find = (needle: keyof WD_ENV): DropDownItem => {
- const wow = envGet(needle, this.props.values);
- const ok = SPECIAL_VALUE_DDI[wow];
- return ok || NULL_CHOICE;
- };
+ find = (conf: keyof WD_ENV): DropDownItem =>
+ SPECIAL_VALUE_DDI[this.getValue(conf)] || NULL_CHOICE
render() {
- return
-
- {t("Invert Hue Range Selection")}
-
-
-
- this.props.onChange("CAMERA_CALIBRATION_invert_hue_selection",
- e.currentTarget.checked ?
- SPECIAL_VALUES.TRUE : SPECIAL_VALUES.FALSE)} />
-
-
-
- {t(`Calibration Object Separation along axis`)}
-
-
-
-
+ return
+ {!this.simple &&
+
+
-
-
-
-
-
-
- {t(`Origin Location in Image`)}
-
-
+ conf={"CAMERA_CALIBRATION_calibration_object_separation"}
+ label={t(`Calibration Object Separation`)} />
+
+ {t(`Calibration Object Separation along axis`)}
+
+
+
+
+
+
+
+
+
+
+
+ {t(`Origin Location in Image`)}
+
+
+ }
{
;
}
}
+
+export interface BoolConfigProps {
+ configKey: keyof WD_ENV;
+ label: string;
+ wDEnv: Partial;
+ onChange(key: keyof WD_ENV, value: number): void;
+}
+
+export const BoolConfig = (props: BoolConfigProps) =>
+
+
+ {t(props.label)}
+
+
+ props.onChange(props.configKey,
+ e.currentTarget.checked ?
+ SPECIAL_VALUES.TRUE : SPECIAL_VALUES.FALSE)} />
+
;
diff --git a/frontend/farmware/weed_detector/index.tsx b/frontend/farmware/weed_detector/index.tsx
index 23da0c179..aa4d8c8db 100644
--- a/frontend/farmware/weed_detector/index.tsx
+++ b/frontend/farmware/weed_detector/index.tsx
@@ -79,29 +79,24 @@ export class WeedDetector
-
- this.props.dispatch(scanImage(id))}
- onFlip={uuid => this.props.dispatch(selectImage(uuid))}
- currentImage={this.props.currentImage}
- images={this.props.images}
- onChange={this.change}
- timeSettings={this.props.timeSettings}
- iteration={wDEnvGet(this.namespace("iteration"))}
- morph={wDEnvGet(this.namespace("morph"))}
- blur={wDEnvGet(this.namespace("blur"))}
- H_LO={wDEnvGet(this.namespace("H_LO"))}
- H_HI={wDEnvGet(this.namespace("H_HI"))}
- S_LO={wDEnvGet(this.namespace("S_LO"))}
- S_HI={wDEnvGet(this.namespace("S_HI"))}
- V_LO={wDEnvGet(this.namespace("V_LO"))}
- V_HI={wDEnvGet(this.namespace("V_HI"))} />
-
+ this.props.dispatch(scanImage(id))}
+ onFlip={uuid => this.props.dispatch(selectImage(uuid))}
+ currentImage={this.props.currentImage}
+ images={this.props.images}
+ onChange={this.change}
+ timeSettings={this.props.timeSettings}
+ iteration={wDEnvGet(this.namespace("iteration"))}
+ morph={wDEnvGet(this.namespace("morph"))}
+ blur={wDEnvGet(this.namespace("blur"))}
+ H_LO={wDEnvGet(this.namespace("H_LO"))}
+ H_HI={wDEnvGet(this.namespace("H_HI"))}
+ S_LO={wDEnvGet(this.namespace("S_LO"))}
+ S_HI={wDEnvGet(this.namespace("S_HI"))}
+ V_LO={wDEnvGet(this.namespace("V_LO"))}
+ V_HI={wDEnvGet(this.namespace("V_HI"))} />
;
diff --git a/frontend/farmware/weed_detector/remote_env/__tests__/translators_test.ts b/frontend/farmware/weed_detector/remote_env/__tests__/translators_test.ts
index 3e9696200..825ee5f38 100644
--- a/frontend/farmware/weed_detector/remote_env/__tests__/translators_test.ts
+++ b/frontend/farmware/weed_detector/remote_env/__tests__/translators_test.ts
@@ -40,6 +40,11 @@ describe("formatEnvKey()", () => {
v: SPECIAL_VALUES.FALSE,
r: "FALSE"
},
+ {
+ k: "CAMERA_CALIBRATION_easy_calibration",
+ v: SPECIAL_VALUES.FALSE,
+ r: "FALSE"
+ },
{
k: "CAMERA_CALIBRATION_calibration_along_axis",
v: SPECIAL_VALUES.X,
diff --git a/frontend/farmware/weed_detector/remote_env/constants.ts b/frontend/farmware/weed_detector/remote_env/constants.ts
index 762fbb480..1c9dced3c 100644
--- a/frontend/farmware/weed_detector/remote_env/constants.ts
+++ b/frontend/farmware/weed_detector/remote_env/constants.ts
@@ -25,6 +25,7 @@ export const WD_KEY_DEFAULTS = {
CAMERA_CALIBRATION_calibration_along_axis: SPECIAL_VALUES.X,
CAMERA_CALIBRATION_image_bot_origin_location: SPECIAL_VALUES.BOTTOM_LEFT,
CAMERA_CALIBRATION_invert_hue_selection: SPECIAL_VALUES.TRUE,
+ CAMERA_CALIBRATION_easy_calibration: SPECIAL_VALUES.FALSE,
CAMERA_CALIBRATION_blur: 5,
CAMERA_CALIBRATION_calibration_object_separation: 100,
CAMERA_CALIBRATION_camera_offset_x: 50,
@@ -61,6 +62,7 @@ export const DEFAULT_FORMATTER: Translation = {
case "CAMERA_CALIBRATION_calibration_along_axis":
case "CAMERA_CALIBRATION_image_bot_origin_location":
case "CAMERA_CALIBRATION_invert_hue_selection":
+ case "CAMERA_CALIBRATION_easy_calibration":
return ("" + (SPECIAL_VALUES[val] || val));
default:
return val;
diff --git a/frontend/front_page/laptop_splash.tsx b/frontend/front_page/laptop_splash.tsx
index ec4308bfa..bc9ddb94f 100644
--- a/frontend/front_page/laptop_splash.tsx
+++ b/frontend/front_page/laptop_splash.tsx
@@ -1,6 +1,5 @@
import * as React from "react";
-const VIDEO_URL = "https://cdn.shopify.com/s/files/1/2040/0289/files/" +
- "Farm_Designer_Loop.mp4?9552037556691879018";
+import { ExternalUrl } from "../external_urls";
export const LaptopSplash = ({ className }: { className: string }) =>
@@ -8,7 +7,7 @@ export const LaptopSplash = ({ className }: { className: string }) =>
-
+
diff --git a/frontend/logs/__tests__/state_to_props_test.ts b/frontend/logs/__tests__/state_to_props_test.ts
index 96d72a6a2..646a76860 100644
--- a/frontend/logs/__tests__/state_to_props_test.ts
+++ b/frontend/logs/__tests__/state_to_props_test.ts
@@ -24,24 +24,10 @@ describe("mapStateToProps()", () => {
state.bot.hardware.configuration.sequence_init_log = false;
const fakeApiConfig = fakeFbosConfig();
fakeApiConfig.body.sequence_init_log = true;
- fakeApiConfig.body.api_migrated = true;
state.resources = buildResourceIndex([fakeApiConfig]);
const props = mapStateToProps(state);
expect(props.sourceFbosConfig("sequence_init_log")).toEqual({
value: true, consistent: false
});
});
-
- it("bot source of FBOS settings", () => {
- const state = fakeState();
- state.bot.hardware.configuration.sequence_init_log = false;
- const fakeApiConfig = fakeFbosConfig();
- fakeApiConfig.body.sequence_init_log = true;
- fakeApiConfig.body.api_migrated = false;
- state.resources = buildResourceIndex([fakeApiConfig]);
- const props = mapStateToProps(state);
- expect(props.sourceFbosConfig("sequence_init_log")).toEqual({
- value: false, consistent: true
- });
- });
});
diff --git a/frontend/messages/__tests__/state_to_props_test.ts b/frontend/messages/__tests__/state_to_props_test.ts
index 9c38170cf..bb509a482 100644
--- a/frontend/messages/__tests__/state_to_props_test.ts
+++ b/frontend/messages/__tests__/state_to_props_test.ts
@@ -18,7 +18,6 @@ describe("mapStateToProps()", () => {
it("returns firmware value", () => {
const state = fakeState();
const fbosConfig = fakeFbosConfig();
- fbosConfig.body.api_migrated = true;
fbosConfig.body.firmware_hardware = "arduino";
state.resources = buildResourceIndex([fbosConfig]);
const props = mapStateToProps(state);
diff --git a/frontend/nav/__tests__/nav_links_test.tsx b/frontend/nav/__tests__/nav_links_test.tsx
index 31d7eeb71..3550df51c 100644
--- a/frontend/nav/__tests__/nav_links_test.tsx
+++ b/frontend/nav/__tests__/nav_links_test.tsx
@@ -28,7 +28,7 @@ describe("
", () => {
});
it("shows links", () => {
- mockDev = true;
+ mockDev = false;
const wrapper = mount(
);
expect(wrapper.text().toLowerCase()).not.toContain("tools");
});
diff --git a/frontend/nav/additional_menu.tsx b/frontend/nav/additional_menu.tsx
index 83a15f9c8..4e0b1290f 100644
--- a/frontend/nav/additional_menu.tsx
+++ b/frontend/nav/additional_menu.tsx
@@ -3,6 +3,7 @@ import { AccountMenuProps } from "./interfaces";
import { Link } from "../link";
import { shortRevision } from "../util";
import { t } from "../i18next_wrapper";
+import { ExternalUrl } from "../external_urls";
export const AdditionalMenu = (props: AccountMenuProps) => {
return
@@ -30,7 +31,7 @@ export const AdditionalMenu = (props: AccountMenuProps) => {
diff --git a/frontend/nav/nav_links.tsx b/frontend/nav/nav_links.tsx
index e05777dab..e4d7bc76c 100644
--- a/frontend/nav/nav_links.tsx
+++ b/frontend/nav/nav_links.tsx
@@ -37,7 +37,7 @@ export const getLinks = (): NavLinkParams[] => betterCompact([
name: "Regimens", icon: "calendar-check-o", slug: "regimens",
computeHref: computeEditorUrlFromState("Regimen")
},
- DevSettings.futureFeaturesEnabled() ? undefined :
+ !DevSettings.futureFeaturesEnabled() ? undefined :
{ name: "Tools", icon: "wrench", slug: "tools" },
{
name: "Farmware", icon: "crosshairs", slug: "farmware",
diff --git a/frontend/open_farm/__tests__/icons_test.ts b/frontend/open_farm/__tests__/icons_test.ts
index 499c8e618..153fd1e74 100644
--- a/frontend/open_farm/__tests__/icons_test.ts
+++ b/frontend/open_farm/__tests__/icons_test.ts
@@ -1,10 +1,4 @@
-import { OpenFarmAPI, svgToUrl } from "../icons";
-
-describe("OpenFarmAPI", () => {
- it("has a base URL", () => {
- expect(OpenFarmAPI.OFBaseURL).toContain("openfarm.cc");
- });
-});
+import { svgToUrl } from "../icons";
describe("svgToUrl()", () => {
it("returns svg url", () => {
diff --git a/frontend/open_farm/cached_crop.ts b/frontend/open_farm/cached_crop.ts
index 24dc069ab..21d166b6a 100644
--- a/frontend/open_farm/cached_crop.ts
+++ b/frontend/open_farm/cached_crop.ts
@@ -1,7 +1,8 @@
import axios, { AxiosResponse } from "axios";
import { Dictionary } from "farmbot";
import { isObject } from "lodash";
-import { OFCropAttrs, OFCropResponse, OpenFarmAPI, svgToUrl } from "./icons";
+import { OFCropAttrs, OFCropResponse, svgToUrl } from "./icons";
+import { ExternalUrl } from "../external_urls";
export type OFIcon = Readonly
;
type IconDictionary = Dictionary;
@@ -57,7 +58,7 @@ const cacheTheIcon = (slug: string) =>
};
function HTTPIconFetch(slug: string) {
- const url = OpenFarmAPI.OFBaseURL + slug;
+ const url = ExternalUrl.OpenFarm.cropApi + slug;
// Avoid duplicate requests.
if (promiseCache[url]) { return promiseCache[url]; }
promiseCache[url] = axios
diff --git a/frontend/open_farm/icons.ts b/frontend/open_farm/icons.ts
index c79f93549..b2ed9f940 100644
--- a/frontend/open_farm/icons.ts
+++ b/frontend/open_farm/icons.ts
@@ -1,4 +1,3 @@
-const BASE = "https://openfarm.cc/api/v1/crops/";
export const DATA_URI = "data:image/svg+xml;utf8,";
export const DEFAULT_ICON = "/app-resources/img/generic-plant.svg";
@@ -20,10 +19,6 @@ export interface OFCropResponse {
};
}
-export namespace OpenFarmAPI {
- export const OFBaseURL = BASE;
-}
-
export function svgToUrl(xml: string | undefined): string {
return xml ?
(DATA_URI + encodeURIComponent(xml)) : DEFAULT_ICON;
diff --git a/frontend/os_download/content.tsx b/frontend/os_download/content.tsx
index c30d5c016..f9d0e66f9 100644
--- a/frontend/os_download/content.tsx
+++ b/frontend/os_download/content.tsx
@@ -3,9 +3,7 @@ import axios from "axios";
import { t } from "../i18next_wrapper";
import { GithubRelease } from "../devices/interfaces";
import { Content } from "../constants";
-
-const LATEST_RELEASE_URL =
- "https://api.github.com/repos/farmbot/farmbot_os/releases/latest";
+import { ExternalUrl } from "../external_urls";
interface OsDownloadState {
tagName: string;
@@ -49,7 +47,7 @@ export class OsDownload extends React.Component<{}, OsDownloadState> {
}
fetchLatestRelease = () =>
- axios.get(LATEST_RELEASE_URL)
+ axios.get(ExternalUrl.latestRelease)
.then(resp =>
this.setState({
tagName: resp.data.tag_name,
diff --git a/frontend/redux/upgrade_reminder.ts b/frontend/redux/upgrade_reminder.ts
index f74a2d542..7f97d0d2f 100644
--- a/frontend/redux/upgrade_reminder.ts
+++ b/frontend/redux/upgrade_reminder.ts
@@ -1,10 +1,10 @@
import { info } from "../toast/toast";
-import { semverCompare, SemverResult, MinVersionOverride } from "../util";
+import { semverCompare, SemverResult, FbosVersionFallback } from "../util";
import { Content } from "../constants";
import { Dictionary } from "lodash";
const IDEAL_VERSION =
- globalConfig.FBOS_END_OF_LIFE_VERSION || MinVersionOverride.ALWAYS;
+ globalConfig.FBOS_END_OF_LIFE_VERSION || FbosVersionFallback.NULL;
/** Returns a function that, when given a version string, (possibly) warns the
* user to upgrade FBOS versions before it hits end of life. */
@@ -12,8 +12,8 @@ export function createReminderFn() {
/** FBOS Version can change during the app lifecycle. We only want one
* reminder per FBOS version change. */
const alreadyChecked: Dictionary = {
- // Dont bother when the user is offline.
- [MinVersionOverride.ALWAYS]: true
+ // Don't bother when the user is offline.
+ [FbosVersionFallback.NULL]: true
};
return function reminder(version: string) {
diff --git a/frontend/redux/version_tracker_middleware.ts b/frontend/redux/version_tracker_middleware.ts
index 4af0b8aed..1187b4fe5 100644
--- a/frontend/redux/version_tracker_middleware.ts
+++ b/frontend/redux/version_tracker_middleware.ts
@@ -1,5 +1,5 @@
import { EnvName } from "./interfaces";
-import { determineInstalledOsVersion, MinVersionOverride } from "../util/index";
+import { determineInstalledOsVersion, FbosVersionFallback } from "../util/index";
import { maybeGetDevice } from "../resources/selectors";
import { MW } from "./middlewares";
import { Everything } from "../interfaces";
@@ -11,10 +11,10 @@ const maybeRemindUserToUpdate = createReminderFn();
function getVersionFromState(state: Everything) {
const device = maybeGetDevice(state.resources.index);
- const v =
- determineInstalledOsVersion(state.bot, device) || MinVersionOverride.ALWAYS;
- maybeRemindUserToUpdate(v);
- return v;
+ const version = determineInstalledOsVersion(state.bot, device)
+ || FbosVersionFallback.NULL;
+ maybeRemindUserToUpdate(version);
+ return version;
}
const fn: MW =
diff --git a/frontend/sequences/step_tiles/tile_calibrate.tsx b/frontend/sequences/step_tiles/tile_calibrate.tsx
index ea522b23b..1669f1ae0 100644
--- a/frontend/sequences/step_tiles/tile_calibrate.tsx
+++ b/frontend/sequences/step_tiles/tile_calibrate.tsx
@@ -69,7 +69,7 @@ class InnerTileCalibrate extends React.Component {
return
> {
{t("Please send us an email at contact@farm.bot or see the ")}
-
+
{t("FarmBot forum.")}
diff --git a/frontend/ui/__tests__/doc_link_test.ts b/frontend/ui/__tests__/doc_link_test.ts
index 5a1c61d06..bcb65f595 100644
--- a/frontend/ui/__tests__/doc_link_test.ts
+++ b/frontend/ui/__tests__/doc_link_test.ts
@@ -1,8 +1,9 @@
-import { docLink, BASE_URL } from "../doc_link";
+import { docLink } from "../doc_link";
+import { ExternalUrl } from "../../external_urls";
describe("docLink", () => {
it("creates doc links", () => {
- expect(docLink()).toEqual(BASE_URL);
- expect(docLink("farmware")).toEqual(BASE_URL + "farmware");
+ expect(docLink()).toEqual(ExternalUrl.softwareDocs + "/");
+ expect(docLink("farmware")).toEqual(ExternalUrl.softwareDocs + "/farmware");
});
});
diff --git a/frontend/ui/doc_link.ts b/frontend/ui/doc_link.ts
index a65b5958c..8cbcc2edd 100644
--- a/frontend/ui/doc_link.ts
+++ b/frontend/ui/doc_link.ts
@@ -1,4 +1,4 @@
-export const BASE_URL = "https://software.farm.bot/docs/";
+import { ExternalUrl } from "../external_urls";
/** A centralized list of all documentation slugs in the app makes it easier to
* rename / move links in the future. */
@@ -7,11 +7,13 @@ export const DOC_SLUGS = {
"camera-calibration": "Camera Calibration",
"the-farmbot-web-app": "Web App",
"farmware": "Farmware",
- "connecting-farmbot-to-the-internet": "Connecting FarmBot to the Internet"
+ "connecting-farmbot-to-the-internet": "Connecting FarmBot to the Internet",
+ "for-it-security-professionals": "For IT Security Professionals",
};
export type DocSlug = keyof typeof DOC_SLUGS;
/** WHY?: The function keeps things DRY. It also makes life easier when the
* documentation URL / slug name changes. */
-export const docLink = (slug?: DocSlug) => BASE_URL + (slug || "");
+export const docLink = (slug?: DocSlug) =>
+ `${ExternalUrl.softwareDocs}/${slug || ""}`;
diff --git a/frontend/util/__tests__/version_test.ts b/frontend/util/__tests__/version_test.ts
index 28e9a1e56..e58346c44 100644
--- a/frontend/util/__tests__/version_test.ts
+++ b/frontend/util/__tests__/version_test.ts
@@ -5,6 +5,7 @@ import {
createShouldDisplayFn,
determineInstalledOsVersion,
versionOK,
+ MinVersionOverride,
} from "../version";
import { bot } from "../../__test_support__/fake_state/bot";
import { fakeDevice } from "../../__test_support__/resource_index_builder";
@@ -128,6 +129,10 @@ describe("shouldDisplay()", () => {
expect(createShouldDisplayFn("10.0.0",
{ jest_feature: "1.0.0" }, undefined)(
Feature.jest_feature)).toBeTruthy();
+ globalConfig.FBOS_END_OF_LIFE_VERSION = MinVersionOverride.NEVER;
+ expect(createShouldDisplayFn(undefined, fakeMinOsData, undefined)(
+ Feature.jest_feature)).toBeTruthy();
+ delete globalConfig.FBOS_END_OF_LIFE_VERSION;
});
it("shouldn't display", () => {
@@ -179,14 +184,20 @@ describe("determineInstalledOsVersion()", () => {
describe("versionOK()", () => {
it("checks if major/minor version meets min requirement", () => {
- expect(versionOK("9.1.9-rc99", 3, 0)).toBeTruthy();
- expect(versionOK("3.0.9-rc99", 3, 0)).toBeTruthy();
- expect(versionOK("4.0.0", 3, 0)).toBeTruthy();
- expect(versionOK("4.0.0", 3, 1)).toBeTruthy();
- expect(versionOK("3.1.0", 3, 0)).toBeTruthy();
- expect(versionOK("2.0.-", 3, 0)).toBeFalsy();
- expect(versionOK("2.9.4", 3, 0)).toBeFalsy();
- expect(versionOK("1.9.6", 3, 0)).toBeFalsy();
- expect(versionOK("3.1.6", 4, 0)).toBeFalsy();
+ globalConfig.MINIMUM_FBOS_VERSION = "3.0.0";
+ expect(versionOK("9.1.9-rc99")).toBeTruthy();
+ expect(versionOK("3.0.9-rc99")).toBeTruthy();
+ expect(versionOK("4.0.0")).toBeTruthy();
+ expect(versionOK("3.1.0")).toBeTruthy();
+ expect(versionOK("2.0.-")).toBeFalsy();
+ expect(versionOK("2.9.4")).toBeFalsy();
+ expect(versionOK("1.9.6")).toBeFalsy();
+ globalConfig.MINIMUM_FBOS_VERSION = "3.1.0";
+ expect(versionOK("4.0.0")).toBeTruthy();
+ globalConfig.MINIMUM_FBOS_VERSION = "4.0.0";
+ expect(versionOK("3.1.6")).toBeFalsy();
+ delete globalConfig.MINIMUM_FBOS_VERSION;
+ expect(versionOK("5.0.0")).toBeFalsy();
+ expect(versionOK("7.0.0")).toBeTruthy();
});
});
diff --git a/frontend/util/util.ts b/frontend/util/util.ts
index 247a785e1..33357bb03 100644
--- a/frontend/util/util.ts
+++ b/frontend/util/util.ts
@@ -183,9 +183,7 @@ export function validBotLocationData(
*/
export function validFwConfig(config: TaggedFirmwareConfig | undefined):
TaggedFirmwareConfig["body"] | undefined {
- return (config?.body.api_migrated)
- ? config.body
- : undefined;
+ return config ? config.body : undefined;
}
/**
@@ -193,9 +191,7 @@ export function validFwConfig(config: TaggedFirmwareConfig | undefined):
*/
export function validFbosConfig(
config: TaggedFbosConfig | undefined): TaggedFbosConfig["body"] | undefined {
- return (config?.body.api_migrated)
- ? config.body
- : undefined;
+ return config ? config.body : undefined;
}
interface BetterUUID {
diff --git a/frontend/util/version.ts b/frontend/util/version.ts
index 1a4c6082d..b6178b7d3 100644
--- a/frontend/util/version.ts
+++ b/frontend/util/version.ts
@@ -97,10 +97,13 @@ export function minFwVersionCheck(current: string | undefined, min: string) {
* for shouldDisplay()
*/
export enum MinVersionOverride {
- ALWAYS = "0.0.0",
NEVER = "999.999.999",
}
+export enum FbosVersionFallback {
+ NULL = "0.0.0",
+}
+
/**
* Determine whether a feature should be displayed based on
* the user's current FBOS version. Min FBOS version feature data is pulled
@@ -114,19 +117,18 @@ export function createShouldDisplayFn(
lookupData: MinOsFeatureLookup | undefined,
override: string | undefined) {
return function (feature: Feature): boolean {
- const target = override || current;
- if (isString(target)) {
- const table = lookupData || {};
- const min = table[feature] || MinVersionOverride.NEVER;
- switch (semverCompare(target, min)) {
- case SemverResult.LEFT_IS_GREATER:
- case SemverResult.EQUAL:
- return true;
- default:
- return false;
- }
+ const fallback = globalConfig.FBOS_END_OF_LIFE_VERSION ||
+ FbosVersionFallback.NULL;
+ const target = override || current || fallback;
+ const table = lookupData || {};
+ const min = table[feature] || MinVersionOverride.NEVER;
+ switch (semverCompare(target, min)) {
+ case SemverResult.LEFT_IS_GREATER:
+ case SemverResult.EQUAL:
+ return true;
+ default:
+ return false;
}
- return false;
};
}
@@ -147,6 +149,9 @@ export function determineInstalledOsVersion(
}
}
+const parseVersion = (version: string) =>
+ version.split(".").map(x => parseInt(x, 10));
+
/**
* Compare installed FBOS version against the lowest version compatible
* with the web app to lock out incompatible FBOS versions from the App.
@@ -155,20 +160,16 @@ export function determineInstalledOsVersion(
* identifiers.
*
* @param stringyVersion version string to check ("0.0.0")
- * @param _EXPECTED_MAJOR minimum required major version number
- * @param _EXPECTED_MINOR minimum required minor version number
*/
-export function versionOK(stringyVersion = "0.0.0",
- _EXPECTED_MAJOR: number,
- _EXPECTED_MINOR: number) {
- const [actual_major, actual_minor] = stringyVersion
- .split(".")
- .map(x => parseInt(x, 10));
- if (actual_major > _EXPECTED_MAJOR) {
+export function versionOK(stringyVersion = "0.0.0") {
+ const [actual_major, actual_minor] = parseVersion(stringyVersion);
+ const [EXPECTED_MAJOR, EXPECTED_MINOR] =
+ parseVersion(globalConfig.MINIMUM_FBOS_VERSION || "6.0.0");
+ if (actual_major > EXPECTED_MAJOR) {
return true;
} else {
- const majorOK = (actual_major == _EXPECTED_MAJOR);
- const minorOK = (actual_minor >= _EXPECTED_MINOR);
+ const majorOK = (actual_major == EXPECTED_MAJOR);
+ const minorOK = (actual_minor >= EXPECTED_MINOR);
return (majorOK && minorOK);
}
}
diff --git a/spec/controllers/api/devices/devices_controller_seed_spec.rb b/spec/controllers/api/devices/devices_controller_seed_spec.rb
index 259e07667..f8a71103c 100644
--- a/spec/controllers/api/devices/devices_controller_seed_spec.rb
+++ b/spec/controllers/api/devices/devices_controller_seed_spec.rb
@@ -101,6 +101,14 @@ describe Api::DevicesController do
device.tool_slots.order(id: :asc)[5]
end
+ def tool_slots_slot_7?(device)
+ device.tool_slots.order(id: :asc)[6]
+ end
+
+ def tool_slots_slot_8?(device)
+ device.tool_slots.order(id: :asc)[7]
+ end
+
def tools_seed_bin?(device)
device.tools.find_by(name: "Seed Bin")
end
@@ -117,10 +125,6 @@ describe Api::DevicesController do
device.tools.find_by(name: "Seed Trough 2")
end
- def tools_seed_trough_3?(device)
- device.tools.find_by(name: "Seed Trough 3")
- end
-
def tools_seeder?(device)
device.tools.find_by(name: "Seeder")
end
@@ -230,17 +234,20 @@ describe Api::DevicesController do
expect(tool_slots_slot_4?(device).name).to eq("Watering Nozzle")
expect(tool_slots_slot_5?(device).name).to eq("Soil Sensor")
expect(tool_slots_slot_6?(device).name).to eq("Weeder")
+ expect(tool_slots_slot_7?(device)).to_not be
+ expect(tool_slots_slot_8?(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), "Weeder")
+
expect(tools_seed_bin?(device)).to be
expect(tools_seed_tray?(device)).to be
expect(tools_seed_trough_1?(device)).to_not be
expect(tools_seed_trough_2?(device)).to_not be
- expect(tools_seed_trough_3?(device)).to_not 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)
@@ -280,6 +287,8 @@ describe Api::DevicesController do
expect(tool_slots_slot_4?(device).name).to eq("Watering Nozzle")
expect(tool_slots_slot_5?(device).name).to eq("Soil Sensor")
expect(tool_slots_slot_6?(device).name).to eq("Weeder")
+ expect(tool_slots_slot_7?(device)).to_not be
+ expect(tool_slots_slot_8?(device)).to_not be
check_slot_pairing(tool_slots_slot_1?(device), "Seeder")
check_slot_pairing(tool_slots_slot_2?(device), "Seed Bin")
@@ -287,11 +296,11 @@ describe Api::DevicesController do
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), "Weeder")
+
expect(tools_seed_bin?(device)).to be
expect(tools_seed_tray?(device)).to be
expect(tools_seed_trough_1?(device)).to_not be
expect(tools_seed_trough_2?(device)).to_not be
- expect(tools_seed_trough_3?(device)).to_not 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)
@@ -331,11 +340,20 @@ describe Api::DevicesController do
expect(tool_slots_slot_4?(device).name).to eq("Watering Nozzle")
expect(tool_slots_slot_5?(device).name).to eq("Soil Sensor")
expect(tool_slots_slot_6?(device).name).to eq("Weeder")
+ expect(tool_slots_slot_7?(device)).to_not be
+ expect(tool_slots_slot_8?(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), "Weeder")
+
expect(tools_seed_bin?(device)).to be
expect(tools_seed_tray?(device)).to be
expect(tools_seed_trough_1?(device)).to_not be
expect(tools_seed_trough_2?(device)).to_not be
- expect(tools_seed_trough_3?(device)).to_not 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)
@@ -375,11 +393,22 @@ describe Api::DevicesController do
expect(tool_slots_slot_4?(device).name).to eq("Watering Nozzle")
expect(tool_slots_slot_5?(device).name).to eq("Soil Sensor")
expect(tool_slots_slot_6?(device).name).to eq("Weeder")
+ expect(tool_slots_slot_7?(device).name).to eq("Seed Trough 1")
+ expect(tool_slots_slot_8?(device).name).to eq("Seed Trough 2")
+
+ 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), "Weeder")
+ 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_not be
- expect(tools_seed_trough_2?(device)).to_not be
- expect(tools_seed_trough_3?(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 be_kind_of(Tool)
expect(tools_soil_sensor?(device)).to be_kind_of(Tool)
expect(tools_watering_nozzle?(device)).to be_kind_of(Tool)
@@ -419,6 +448,8 @@ describe Api::DevicesController do
expect(tool_slots_slot_4?(device).name).to eq("Watering Nozzle")
expect(tool_slots_slot_5?(device).name).to eq("Soil Sensor")
expect(tool_slots_slot_6?(device).name).to eq("Weeder")
+ expect(tool_slots_slot_7?(device)).to_not be
+ expect(tool_slots_slot_8?(device)).to_not be
check_slot_pairing(tool_slots_slot_1?(device), "Seeder")
check_slot_pairing(tool_slots_slot_2?(device), "Seed Bin")
@@ -431,7 +462,6 @@ describe Api::DevicesController do
expect(tools_seed_tray?(device)).to be
expect(tools_seed_trough_1?(device)).to_not be
expect(tools_seed_trough_2?(device)).to_not be
- expect(tools_seed_trough_3?(device)).to_not 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)
@@ -471,6 +501,8 @@ describe Api::DevicesController do
expect(tool_slots_slot_4?(device).name).to eq("Watering Nozzle")
expect(tool_slots_slot_5?(device).name).to eq("Soil Sensor")
expect(tool_slots_slot_6?(device).name).to eq("Weeder")
+ expect(tool_slots_slot_7?(device).name).to eq("Seed Trough 1")
+ expect(tool_slots_slot_8?(device).name).to eq("Seed Trough 2")
check_slot_pairing(tool_slots_slot_1?(device), "Seeder")
check_slot_pairing(tool_slots_slot_2?(device), "Seed Bin")
@@ -478,12 +510,13 @@ describe Api::DevicesController do
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), "Weeder")
+ 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_not be
- expect(tools_seed_trough_2?(device)).to_not be
- expect(tools_seed_trough_3?(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 be_kind_of(Tool)
expect(tools_soil_sensor?(device)).to be_kind_of(Tool)
expect(tools_watering_nozzle?(device)).to be_kind_of(Tool)
@@ -519,18 +552,20 @@ describe Api::DevicesController do
expect(settings_hide_sensors?(device)).to be(true)
expect(tool_slots_slot_1?(device).name).to eq("Seed Trough 1")
expect(tool_slots_slot_2?(device).name).to eq("Seed Trough 2")
- expect(tool_slots_slot_3?(device).name).to eq("Seed Trough 3")
+ 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
+
check_slot_pairing(tool_slots_slot_1?(device), "Seed Trough 1")
check_slot_pairing(tool_slots_slot_2?(device), "Seed Trough 2")
- check_slot_pairing(tool_slots_slot_3?(device), "Seed Trough 3")
+
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_seed_trough_3?(device)).to be
expect(tools_seeder?(device)).to_not be
expect(tools_soil_sensor?(device)).to_not be
expect(tools_watering_nozzle?(device)).to_not be
@@ -566,18 +601,20 @@ describe Api::DevicesController do
expect(settings_hide_sensors?(device)).to be(true)
expect(tool_slots_slot_1?(device).name).to eq("Seed Trough 1")
expect(tool_slots_slot_2?(device).name).to eq("Seed Trough 2")
- expect(tool_slots_slot_3?(device).name).to eq("Seed Trough 3")
+ 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
+
check_slot_pairing(tool_slots_slot_1?(device), "Seed Trough 1")
check_slot_pairing(tool_slots_slot_2?(device), "Seed Trough 2")
- check_slot_pairing(tool_slots_slot_3?(device), "Seed Trough 3")
+
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_seed_trough_3?(device)).to be
expect(tools_seeder?(device)).to_not be
expect(tools_soil_sensor?(device)).to_not be
expect(tools_watering_nozzle?(device)).to_not be