commit
19798b894f
|
@ -284,6 +284,8 @@ export namespace ToolTips {
|
|||
export const TAKE_PHOTO =
|
||||
trim(`Snaps a photo using the device camera. Select the camera type
|
||||
on the Device page.`);
|
||||
export const EMERGENCY_LOCK =
|
||||
trim(`Stops a device from moving until it is unlocked by a user.`);
|
||||
|
||||
export const SELECT_A_CAMERA =
|
||||
trim(`Select a camera on the Device page to take photos.`);
|
||||
|
@ -294,6 +296,9 @@ export namespace ToolTips {
|
|||
For example, you can mark a plant as "planted" during a seeding
|
||||
sequence or delete a weed after removing it.`);
|
||||
|
||||
export const REBOOT =
|
||||
trim(`Power cycle FarmBot's onboard computer or microcontroller.`);
|
||||
|
||||
export const SET_SERVO_ANGLE =
|
||||
trim(`Move a servo to the provided angle.`);
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
jest.mock("../../api/crud", () => {
|
||||
return { editStep: jest.fn() };
|
||||
});
|
||||
import { TileReboot, editTheRebootStep } from "../step_tiles/tile_reboot";
|
||||
import { render } from "enzyme";
|
||||
import React from "react";
|
||||
import { StepParams } from "../interfaces";
|
||||
import { fakeSequence } from "../../__test_support__/fake_state/resources";
|
||||
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
|
||||
import { editStep } from "../../api/crud";
|
||||
|
||||
const fakeProps = (): StepParams => {
|
||||
const currentSequence = fakeSequence();
|
||||
const resources = buildResourceIndex().index;
|
||||
|
||||
return {
|
||||
currentSequence,
|
||||
currentStep: {
|
||||
kind: "reboot",
|
||||
args: {
|
||||
package: "farmbot_os"
|
||||
}
|
||||
},
|
||||
dispatch: jest.fn(),
|
||||
index: 1,
|
||||
resources,
|
||||
confirmStepDeletion: false,
|
||||
};
|
||||
};
|
||||
|
||||
describe("<TileReboot/>", () => {
|
||||
it("renders", () => {
|
||||
const el = render(<TileReboot {...fakeProps()} />);
|
||||
const verbiage = el.text();
|
||||
expect(verbiage).toContain("Entire system");
|
||||
expect(verbiage).toContain("Just the Arduino");
|
||||
});
|
||||
|
||||
it("crashes if the step is of the wrong `kind`", () => {
|
||||
const props = fakeProps();
|
||||
props.currentStep = { kind: "sync", args: {} };
|
||||
const boom = () => TileReboot(props);
|
||||
expect(boom).toThrowError();
|
||||
});
|
||||
|
||||
it("edits the reboot step", () => {
|
||||
const props = fakeProps();
|
||||
const editFn = editTheRebootStep(props);
|
||||
editFn("arduino_firmware");
|
||||
expect(props.dispatch).toHaveBeenCalled();
|
||||
expect(editStep).toHaveBeenCalledWith({
|
||||
step: props.currentStep,
|
||||
index: props.index,
|
||||
sequence: props.currentSequence,
|
||||
executor: expect.any(Function),
|
||||
});
|
||||
});
|
||||
});
|
|
@ -48,6 +48,16 @@ export function StepButtonCluster(props: StepButtonProps) {
|
|||
color="orange">
|
||||
{t("CONTROL PERIPHERAL")}
|
||||
</StepButton>,
|
||||
<StepButton {...commonStepProps}
|
||||
step={{
|
||||
kind: "toggle_pin",
|
||||
args: {
|
||||
pin_number: 4
|
||||
}
|
||||
}}
|
||||
color="yellow">
|
||||
{t("TOGGLE PERIPHERAL")}
|
||||
</StepButton>,
|
||||
<StepButton {...commonStepProps}
|
||||
step={{
|
||||
kind: "read_pin",
|
||||
|
@ -60,6 +70,17 @@ export function StepButtonCluster(props: StepButtonProps) {
|
|||
color="yellow">
|
||||
{t("READ SENSOR")}
|
||||
</StepButton>,
|
||||
<StepButton {...commonStepProps}
|
||||
step={{
|
||||
kind: "set_servo_angle",
|
||||
args: {
|
||||
pin_number: 4,
|
||||
pin_value: 90
|
||||
}
|
||||
}}
|
||||
color="blue">
|
||||
{t("CONTROL SERVO")}
|
||||
</StepButton>,
|
||||
<StepButton {...commonStepProps}
|
||||
step={{
|
||||
kind: "wait",
|
||||
|
@ -79,6 +100,16 @@ export function StepButtonCluster(props: StepButtonProps) {
|
|||
color="red">
|
||||
{t("SEND MESSAGE")}
|
||||
</StepButton>,
|
||||
<StepButton {...commonStepProps}
|
||||
step={{ kind: "emergency_lock", args: {} }}
|
||||
color="brown">
|
||||
{t("EMERGENCY LOCK")}
|
||||
</StepButton>,
|
||||
<StepButton {...commonStepProps}
|
||||
step={{ kind: "reboot", args: { package: "farmbot_os" } }}
|
||||
color="brown">
|
||||
{t("REBOOT")}
|
||||
</StepButton>,
|
||||
<StepButton{...commonStepProps}
|
||||
step={{
|
||||
kind: "find_home",
|
||||
|
|
|
@ -5,12 +5,7 @@ jest.mock("../../../api/crud", () => ({
|
|||
import { remove, move, splice, renderCeleryNode } from "../index";
|
||||
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
|
||||
import { overwrite } from "../../../api/crud";
|
||||
import {
|
||||
Wait, If, ExecuteScript, Execute, FindHome, MoveAbsolute, SendMessage,
|
||||
TakePhoto, SetServoAngle, TogglePin, Zero, Calibrate, Home, Reboot,
|
||||
CheckUpdates, FactoryReset, Sync, DumpInfo, PowerOff, ReadStatus,
|
||||
EmergencyLock, EmergencyUnlock, InstallFirstPartyFarmware
|
||||
} from "farmbot";
|
||||
import { SequenceBodyItem, Wait } from "farmbot";
|
||||
import { mount } from "enzyme";
|
||||
import { StepParams, MessageType } from "../../interfaces";
|
||||
import { emptyState } from "../../../resources/reducer";
|
||||
|
@ -96,30 +91,105 @@ describe("renderCeleryNode()", () => {
|
|||
confirmStepDeletion: false,
|
||||
});
|
||||
|
||||
const TEST_DATA = [
|
||||
interface TestData {
|
||||
node: SequenceBodyItem;
|
||||
expected: string;
|
||||
}
|
||||
|
||||
const TEST_DATA: TestData[] = [
|
||||
{
|
||||
expected: "MarkPlantasx = 300",
|
||||
node: {
|
||||
kind: "resource_update",
|
||||
args: {
|
||||
resource_id: 23,
|
||||
resource_type: "Plant",
|
||||
label: "x",
|
||||
value: 300
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
expected: "System",
|
||||
node: {
|
||||
kind: "check_updates",
|
||||
args: {
|
||||
package: "farmbot_os"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
expected: "System",
|
||||
node: {
|
||||
kind: "factory_reset",
|
||||
args: {
|
||||
package: "farmbot_os"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
expected: "Unlocking a device requires user intervention",
|
||||
node: {
|
||||
kind: "emergency_lock",
|
||||
args: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
expected: "Unable to properly display this step",
|
||||
node: {
|
||||
kind: "power_off",
|
||||
args: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
expected: "Unable to properly display this step",
|
||||
node: { kind: "read_status", args: {} }
|
||||
},
|
||||
{
|
||||
expected: "",
|
||||
node: {
|
||||
kind: "install_first_party_farmware",
|
||||
args: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
node: {
|
||||
kind: "_if",
|
||||
args: {
|
||||
lhs: "pin0", op: "is", rhs: 0, _then: { kind: "nothing", args: {} },
|
||||
lhs: "pin0",
|
||||
op: "is",
|
||||
rhs: 0,
|
||||
_then: { kind: "nothing", args: {} },
|
||||
_else: { kind: "nothing", args: {} }
|
||||
}
|
||||
} as If, expected: "Then Execute"
|
||||
},
|
||||
expected: "Then Execute"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
kind: "execute_script",
|
||||
args: { label: "farmware-to-execute" }
|
||||
} as ExecuteScript, expected: "Manual Input"
|
||||
},
|
||||
expected: "Manual Input"
|
||||
},
|
||||
{
|
||||
node: { kind: "execute", args: { sequence_id: 0 } } as Execute,
|
||||
node: {
|
||||
kind: "execute",
|
||||
args: {
|
||||
sequence_id: 0
|
||||
}
|
||||
},
|
||||
expected: "Select a sequence"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
kind: "find_home", args: { speed: 100, axis: "all" }
|
||||
} as FindHome, expected: "Find x"
|
||||
kind: "find_home",
|
||||
args: {
|
||||
speed: 100,
|
||||
axis: "all"
|
||||
}
|
||||
},
|
||||
expected: "Find x"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
|
@ -129,7 +199,8 @@ describe("renderCeleryNode()", () => {
|
|||
speed: 100,
|
||||
offset: { kind: "coordinate", args: { x: 4, y: 5, z: 6 } }
|
||||
}
|
||||
} as MoveAbsolute, expected: "x-Offsety-Offsetz-OffsetSpeed (%)"
|
||||
},
|
||||
expected: "x-Offsety-Offsetz-OffsetSpeed (%)"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
|
@ -138,70 +209,124 @@ describe("renderCeleryNode()", () => {
|
|||
message: "send this message",
|
||||
message_type: MessageType.info
|
||||
}
|
||||
} as SendMessage, expected: "Message"
|
||||
},
|
||||
expected: "Message"
|
||||
},
|
||||
{ node: { kind: "take_photo", args: {} } as TakePhoto, expected: "Photo" },
|
||||
{
|
||||
node: { kind: "wait", args: { milliseconds: 100 } } as Wait,
|
||||
node: {
|
||||
kind: "take_photo",
|
||||
args: {}
|
||||
},
|
||||
expected: "Photo"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
kind: "wait",
|
||||
args: {
|
||||
milliseconds: 100
|
||||
}
|
||||
},
|
||||
expected: "milliseconds"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
kind: "set_servo_angle",
|
||||
args: { pin_number: 4, pin_value: 90, }
|
||||
} as SetServoAngle, expected: "Servo"
|
||||
args: {
|
||||
pin_number: 4,
|
||||
pin_value: 90,
|
||||
}
|
||||
},
|
||||
expected: "Servo"
|
||||
},
|
||||
{
|
||||
node: { kind: "toggle_pin", args: { pin_number: 13 } } as TogglePin,
|
||||
node: {
|
||||
kind: "toggle_pin",
|
||||
args: {
|
||||
pin_number: 13
|
||||
}
|
||||
},
|
||||
expected: "Pin"
|
||||
},
|
||||
{
|
||||
node: { kind: "zero", args: { axis: "all" } } as Zero,
|
||||
node: { kind: "zero", args: { axis: "all" } },
|
||||
expected: "Zero x"
|
||||
},
|
||||
{
|
||||
node: { kind: "calibrate", args: { axis: "all" } } as Calibrate,
|
||||
node: { kind: "calibrate", args: { axis: "all" } },
|
||||
expected: "Calibrate x"
|
||||
},
|
||||
{
|
||||
node: { kind: "home", args: { axis: "all", speed: 100, } } as Home,
|
||||
node: { kind: "home", args: { axis: "all", speed: 100, } },
|
||||
expected: "Home x"
|
||||
},
|
||||
{
|
||||
node: { kind: "reboot", args: { package: "farmbot_os" } } as Reboot,
|
||||
node: { kind: "reboot", args: { package: "farmbot_os" } },
|
||||
expected: "System"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
kind: "check_updates", args: { package: "farmbot_os" }
|
||||
} as CheckUpdates,
|
||||
},
|
||||
expected: "System"
|
||||
},
|
||||
{
|
||||
node: {
|
||||
kind: "factory_reset", args: { package: "farmbot_os" }
|
||||
} as FactoryReset,
|
||||
},
|
||||
expected: "System"
|
||||
},
|
||||
{ node: { kind: "sync", args: {} } as Sync, expected: "" },
|
||||
{ node: { kind: "dump_info", args: {} } as DumpInfo, expected: "" },
|
||||
{ node: { kind: "power_off", args: {} } as PowerOff, expected: "" },
|
||||
{ node: { kind: "read_status", args: {} } as ReadStatus, expected: "" },
|
||||
{
|
||||
node: { kind: "emergency_lock", args: {} } as EmergencyLock,
|
||||
node: {
|
||||
kind: "sync",
|
||||
args: {}
|
||||
},
|
||||
expected: ""
|
||||
},
|
||||
{
|
||||
node: { kind: "emergency_unlock", args: {} } as EmergencyUnlock,
|
||||
node: {
|
||||
kind: "dump_info",
|
||||
args: {}
|
||||
},
|
||||
expected: ""
|
||||
},
|
||||
{
|
||||
node: {
|
||||
kind: "power_off",
|
||||
args: {}
|
||||
},
|
||||
expected: ""
|
||||
},
|
||||
{
|
||||
node: {
|
||||
kind: "read_status",
|
||||
args: {}
|
||||
},
|
||||
expected: ""
|
||||
},
|
||||
{
|
||||
node: { kind: "emergency_lock", args: {} },
|
||||
expected: ""
|
||||
},
|
||||
{
|
||||
node: { kind: "emergency_unlock", args: {} },
|
||||
expected: ""
|
||||
},
|
||||
{
|
||||
node: {
|
||||
kind: "install_first_party_farmware", args: {}
|
||||
} as InstallFirstPartyFarmware, expected: ""
|
||||
},
|
||||
expected: ""
|
||||
},
|
||||
{
|
||||
node: {
|
||||
kind: "unknown",
|
||||
args: {
|
||||
unknown: 0
|
||||
}
|
||||
// tslint:disable-next-line:no-any
|
||||
} as any,
|
||||
expected: "unknown"
|
||||
},
|
||||
// tslint:disable-next-line:no-any
|
||||
{ node: { kind: "unknown", args: { unknown: 0 } } as any, expected: "unknown" },
|
||||
];
|
||||
|
||||
it("renders correct step", () => {
|
||||
|
@ -209,7 +334,8 @@ describe("renderCeleryNode()", () => {
|
|||
const p = fakeProps();
|
||||
p.currentStep = test.node;
|
||||
const step = renderCeleryNode(p);
|
||||
expect(mount(step).text()).toContain(test.expected);
|
||||
const verbiage = mount(step).text().toLowerCase();
|
||||
expect(verbiage).toContain(test.expected.toLowerCase());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,6 +31,8 @@ import { TileCalibrate } from "./tile_calibrate";
|
|||
import { TileMoveHome } from "./tile_move_home";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { TileAssertion } from "./tile_assertion";
|
||||
import { TileEmergencyStop } from "./tile_emergency_stop";
|
||||
import { TileReboot } from "./tile_reboot";
|
||||
|
||||
interface MoveParams {
|
||||
step: Step;
|
||||
|
@ -153,13 +155,15 @@ export function renderCeleryNode(props: StepParams) {
|
|||
case "zero": return <TileSetZero {...props} />;
|
||||
case "calibrate": return <TileCalibrate {...props} />;
|
||||
case "home": return <TileMoveHome {...props} />;
|
||||
case "reboot": case "check_updates": case "factory_reset":
|
||||
return <TileFirmwareAction {...props} />;
|
||||
case "sync": case "dump_info": case "power_off": case "read_status":
|
||||
case "emergency_unlock": case "emergency_lock":
|
||||
case "reboot": return <TileReboot {...props} />;
|
||||
case "emergency_lock": return <TileEmergencyStop {...props} />;
|
||||
case "install_first_party_farmware": return <TileSystemAction {...props} />;
|
||||
case "assertion": return <TileAssertion {...props} />;
|
||||
default: return <TileUnknown {...props} />;
|
||||
case "check_updates":
|
||||
case "factory_reset":
|
||||
return <TileFirmwareAction {...props} />;
|
||||
default:
|
||||
return <TileUnknown {...props} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import * as React from "react";
|
||||
import { StepParams } from "../interfaces";
|
||||
import { ToolTips } from "../../constants";
|
||||
import { StepWrapper, StepHeader, StepContent } from "../step_ui";
|
||||
import { Col, Row } from "../../ui/index";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
|
||||
export function TileEmergencyStop(props: StepParams) {
|
||||
const { dispatch, currentStep, index, currentSequence } = props;
|
||||
const className = "take-photo-step";
|
||||
return <StepWrapper>
|
||||
<StepHeader
|
||||
className={className}
|
||||
helpText={ToolTips.EMERGENCY_LOCK}
|
||||
currentSequence={currentSequence}
|
||||
currentStep={currentStep}
|
||||
dispatch={dispatch}
|
||||
index={index}
|
||||
confirmStepDeletion={props.confirmStepDeletion} />
|
||||
<StepContent className={className}>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<p>
|
||||
{t("Unlocking a device requires user intervention")}
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
</StepContent>
|
||||
</StepWrapper>;
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
import * as React from "react";
|
||||
import { StepParams } from "../interfaces";
|
||||
import { ToolTips } from "../../constants";
|
||||
import { StepWrapper, StepHeader, StepContent } from "../step_ui/index";
|
||||
import { t } from "../../i18next_wrapper";
|
||||
import { Row, Col } from "../../ui";
|
||||
import { ALLOWED_PACKAGES, SequenceBodyItem, Reboot } from "farmbot";
|
||||
import { editStep } from "../../api/crud";
|
||||
|
||||
type StringMap = Record<string, string>;
|
||||
|
||||
interface MultiChoiceRadioProps<T extends StringMap> {
|
||||
uuid: string;
|
||||
choices: T;
|
||||
currentChoice: keyof T;
|
||||
onChange(key: keyof T): void;
|
||||
}
|
||||
|
||||
const MultiChoiceRadio =
|
||||
<T extends StringMap>(props: MultiChoiceRadioProps<T>) => {
|
||||
const choices = Object.keys(props.choices);
|
||||
return <Row>
|
||||
<Col xs={12}>
|
||||
<div className="bottom-content">
|
||||
<div className="channel-fields">
|
||||
<form>
|
||||
{choices.map((choice, i) =>
|
||||
<div key={`${props.uuid} ${i}`} style={{ display: "inline" }}>
|
||||
<label>
|
||||
<input type="radio"
|
||||
value={choice}
|
||||
onChange={() => props.onChange(choice)}
|
||||
checked={props.currentChoice === choice} />
|
||||
{t(props.choices[choice])}
|
||||
</label>
|
||||
</div>)}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>;
|
||||
};
|
||||
|
||||
const PACKAGE_CHOICES: Record<ALLOWED_PACKAGES, string> = {
|
||||
"arduino_firmware": "Just the Arduino",
|
||||
"farmbot_os": "Entire system"
|
||||
};
|
||||
|
||||
function assertReboot(x: SequenceBodyItem): asserts x is Reboot {
|
||||
if (x.kind !== "reboot") {
|
||||
throw new Error("Impossible");
|
||||
}
|
||||
}
|
||||
|
||||
type RELEVANT_KEYS = "currentStep" | "currentSequence" | "index" | "dispatch";
|
||||
type RebootEditProps = Pick<StepParams, RELEVANT_KEYS>;
|
||||
|
||||
export const rebootExecutor =
|
||||
(pkg: ALLOWED_PACKAGES) => (step: SequenceBodyItem) => {
|
||||
assertReboot(step);
|
||||
step.args.package = pkg;
|
||||
};
|
||||
|
||||
export const editTheRebootStep =
|
||||
(props: RebootEditProps) => (pkg: ALLOWED_PACKAGES) => {
|
||||
const { currentStep, index, currentSequence } = props;
|
||||
props.dispatch(editStep({
|
||||
step: currentStep,
|
||||
index,
|
||||
sequence: currentSequence,
|
||||
executor: rebootExecutor(pkg),
|
||||
}));
|
||||
};
|
||||
|
||||
export function TileReboot(props: StepParams) {
|
||||
const { dispatch, currentStep, index, currentSequence } = props;
|
||||
const className = "set-zero-step";
|
||||
assertReboot(currentStep);
|
||||
return <StepWrapper>
|
||||
<StepHeader
|
||||
className={className}
|
||||
helpText={ToolTips.REBOOT}
|
||||
currentSequence={currentSequence}
|
||||
currentStep={currentStep}
|
||||
dispatch={dispatch}
|
||||
index={index}
|
||||
confirmStepDeletion={props.confirmStepDeletion} />
|
||||
<StepContent className={className}>
|
||||
<MultiChoiceRadio
|
||||
uuid={currentSequence.uuid + index}
|
||||
choices={PACKAGE_CHOICES}
|
||||
currentChoice={currentStep.args.package as ALLOWED_PACKAGES}
|
||||
onChange={editTheRebootStep(props)} />
|
||||
</StepContent>
|
||||
</StepWrapper>;
|
||||
}
|
Loading…
Reference in New Issue