add firmware restart button
parent
3d8a06bacf
commit
e9f305e543
|
@ -441,6 +441,9 @@ export namespace Content {
|
|||
trim(`This will restart FarmBot's Raspberry Pi and controller
|
||||
software.`);
|
||||
|
||||
export const RESTART_FIRMWARE =
|
||||
trim(`Restart the Farmduino or Arduino firmware.`);
|
||||
|
||||
export const OS_AUTO_UPDATE =
|
||||
trim(`When enabled, FarmBot OS will periodically check for, download,
|
||||
and install updates automatically.`);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const mockDevice = {
|
||||
send: jest.fn(() => { return Promise.resolve(); })
|
||||
readPin: jest.fn(() => { return Promise.resolve(); })
|
||||
};
|
||||
jest.mock("../../../device", () => ({
|
||||
getDevice: () => (mockDevice)
|
||||
|
@ -55,27 +55,20 @@ describe("<SensorList/>", function () {
|
|||
});
|
||||
|
||||
const expectedPayload = (pin_number: number, pin_mode: 0 | 1) =>
|
||||
expect.objectContaining({
|
||||
kind: "rpc_request",
|
||||
args: expect.objectContaining({ label: expect.any(String) }),
|
||||
body: [expect.objectContaining({
|
||||
kind: "read_pin",
|
||||
args: {
|
||||
pin_number,
|
||||
label: `pin${pin_number}`,
|
||||
pin_mode
|
||||
}
|
||||
})]
|
||||
({
|
||||
pin_number,
|
||||
label: `pin${pin_number}`,
|
||||
pin_mode
|
||||
});
|
||||
|
||||
it("reads pins", () => {
|
||||
const wrapper = mount(<SensorList {...fakeProps()} />);
|
||||
const toggle = wrapper.find("button");
|
||||
toggle.first().simulate("click");
|
||||
expect(mockDevice.send).toHaveBeenCalledWith(expectedPayload(51, 1));
|
||||
expect(mockDevice.readPin).toHaveBeenCalledWith(expectedPayload(51, 1));
|
||||
toggle.last().simulate("click");
|
||||
expect(mockDevice.send).toHaveBeenLastCalledWith(expectedPayload(50, 0));
|
||||
expect(mockDevice.send).toHaveBeenCalledTimes(2);
|
||||
expect(mockDevice.readPin).toHaveBeenLastCalledWith(expectedPayload(50, 0));
|
||||
expect(mockDevice.readPin).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("pins toggles are disabled", () => {
|
||||
|
@ -85,6 +78,6 @@ describe("<SensorList/>", function () {
|
|||
const toggle = wrapper.find("button");
|
||||
toggle.first().simulate("click");
|
||||
toggle.last().simulate("click");
|
||||
expect(mockDevice.send).not.toHaveBeenCalled();
|
||||
expect(mockDevice.readPin).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@ const mockDevice = {
|
|||
powerOff: jest.fn(() => { return Promise.resolve(); }),
|
||||
resetOS: jest.fn(),
|
||||
reboot: jest.fn(() => { return Promise.resolve(); }),
|
||||
send: jest.fn(() => { return Promise.resolve(); }),
|
||||
checkArduinoUpdates: jest.fn(() => { return Promise.resolve(); }),
|
||||
emergencyLock: jest.fn(() => { return Promise.resolve(); }),
|
||||
emergencyUnlock: jest.fn(() => { return Promise.resolve(); }),
|
||||
|
@ -35,7 +36,9 @@ import {
|
|||
fakeSequence, fakeFbosConfig, fakeFirmwareConfig
|
||||
} from "../../__test_support__/fake_state/resources";
|
||||
import { fakeState } from "../../__test_support__/fake_state";
|
||||
import { changeStepSize, resetNetwork, resetConnectionInfo } from "../actions";
|
||||
import {
|
||||
changeStepSize, resetNetwork, resetConnectionInfo, commandErr
|
||||
} from "../actions";
|
||||
import { Actions } from "../../constants";
|
||||
import { buildResourceIndex } from "../../__test_support__/resource_index_builder";
|
||||
import { API } from "../../api/index";
|
||||
|
@ -82,6 +85,14 @@ describe("reboot()", function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe("restartFirmware()", function () {
|
||||
it("calls restartFirmware", async () => {
|
||||
await actions.restartFirmware();
|
||||
expect(mockDevice.send).toHaveBeenCalled();
|
||||
expect(success).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("emergencyLock() / emergencyUnlock", function () {
|
||||
it("calls emergencyLock", () => {
|
||||
actions.emergencyLock();
|
||||
|
@ -249,6 +260,18 @@ describe("isLog()", function () {
|
|||
expect(actions.isLog({})).toBe(false);
|
||||
expect(actions.isLog({ message: "foo" })).toBe(true);
|
||||
});
|
||||
|
||||
it("filters sensitive logs", () => {
|
||||
expect(() => actions.isLog({ message: "NERVESPSKWPASSWORD" }))
|
||||
.toThrowError(/Refusing to display log/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("commandErr()", () => {
|
||||
it("sends toast", () => {
|
||||
commandErr()();
|
||||
expect(error).toHaveBeenCalledWith("Command failed");
|
||||
});
|
||||
});
|
||||
|
||||
describe("toggleControlPanel()", function () {
|
||||
|
|
|
@ -91,6 +91,16 @@ export function reboot() {
|
|||
.then(commandOK(noun), commandErr(noun));
|
||||
}
|
||||
|
||||
export function restartFirmware() {
|
||||
const noun = "Restart Firmware";
|
||||
getDevice() // TODO: add `restartFirmware()` to FBJS
|
||||
.send(rpcRequest([{
|
||||
kind: "reboot",
|
||||
args: { package: "arduino_firmware" }
|
||||
}]))
|
||||
.then(commandOK(noun), commandErr(noun));
|
||||
}
|
||||
|
||||
export function emergencyLock() {
|
||||
const noun = "Emergency stop";
|
||||
getDevice()
|
||||
|
@ -297,9 +307,7 @@ export function pinToggle(pin_number: number) {
|
|||
export function readPin(pin_number: number, label: string, pin_mode: number) {
|
||||
const noun = "Read pin";
|
||||
return getDevice()
|
||||
.send(rpcRequest([{
|
||||
kind: "read_pin", args: { pin_number, label, pin_mode }
|
||||
}]))
|
||||
.readPin({ pin_number, label, pin_mode })
|
||||
.then(_.noop, commandErr(noun));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const mockDevice = {
|
||||
updateConfig: jest.fn(() => { return Promise.resolve(); }),
|
||||
send: jest.fn(() => { return Promise.resolve(); }),
|
||||
};
|
||||
jest.mock("../../../../device", () => ({
|
||||
getDevice: () => (mockDevice)
|
||||
|
@ -12,6 +13,7 @@ import { PowerAndResetProps } from "../interfaces";
|
|||
import { bot } from "../../../../__test_support__/fake_state/bot";
|
||||
import { panelState } from "../../../../__test_support__/control_panel_state";
|
||||
import { fakeState } from "../../../../__test_support__/fake_state";
|
||||
import { clickButton } from "../../../../__test_support__/helpers";
|
||||
|
||||
describe("<PowerAndReset/>", () => {
|
||||
const fakeProps = (): PowerAndResetProps => {
|
||||
|
@ -34,6 +36,9 @@ describe("<PowerAndReset/>", () => {
|
|||
"Automatic Factory Reset", "Connection Attempt Period"]
|
||||
.map(string => expect(wrapper.text().toLowerCase())
|
||||
.toContain(string.toLowerCase()));
|
||||
["Restart Firmware", "Change Ownership"]
|
||||
.map(string => expect(wrapper.text().toLowerCase())
|
||||
.not.toContain(string.toLowerCase()));
|
||||
});
|
||||
|
||||
it("closed", () => {
|
||||
|
@ -61,8 +66,35 @@ describe("<PowerAndReset/>", () => {
|
|||
const p = fakeProps();
|
||||
p.controlPanelState.power_and_reset = true;
|
||||
const wrapper = mount(<PowerAndReset {...p} />);
|
||||
wrapper.find("button").at(3).simulate("click");
|
||||
clickButton(wrapper, 3, "yes");
|
||||
expect(mockDevice.updateConfig)
|
||||
.toHaveBeenCalledWith({ disable_factory_reset: true });
|
||||
});
|
||||
|
||||
it("restarts firmware", () => {
|
||||
const p = fakeProps();
|
||||
p.controlPanelState.power_and_reset = true;
|
||||
p.shouldDisplay = () => true;
|
||||
const wrapper = mount(<PowerAndReset {...p} />);
|
||||
expect(wrapper.text().toLowerCase())
|
||||
.toContain("Restart Firmware".toLowerCase());
|
||||
clickButton(wrapper, 2, "restart");
|
||||
expect(mockDevice.send).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
kind: "rpc_request",
|
||||
args: expect.objectContaining({ label: expect.any(String) }),
|
||||
body: [expect.objectContaining({
|
||||
kind: "reboot", args: { package: "arduino_firmware" }
|
||||
})]
|
||||
}));
|
||||
});
|
||||
|
||||
it("shows change ownership button", () => {
|
||||
const p = fakeProps();
|
||||
p.controlPanelState.power_and_reset = true;
|
||||
p.shouldDisplay = () => true;
|
||||
const wrapper = mount(<PowerAndReset {...p} />);
|
||||
expect(wrapper.text().toLowerCase())
|
||||
.toContain("Change Ownership".toLowerCase());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col } from "../../../ui";
|
||||
import { t } from "i18next";
|
||||
import { ColWidth } from "../farmbot_os_settings";
|
||||
|
||||
export interface FbosButtonRowProps {
|
||||
botOnline: boolean;
|
||||
label: string;
|
||||
description: string;
|
||||
buttonText: string;
|
||||
color: string;
|
||||
action: () => void;
|
||||
}
|
||||
|
||||
export const FbosButtonRow = (props: FbosButtonRowProps) => {
|
||||
return <Row>
|
||||
<Col xs={ColWidth.label}>
|
||||
<label>
|
||||
{t(props.label)}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={ColWidth.description}>
|
||||
<p>
|
||||
{t(props.description)}
|
||||
</p>
|
||||
</Col>
|
||||
<Col xs={ColWidth.button}>
|
||||
<button
|
||||
className={`fb-button ${props.color}`}
|
||||
type="button"
|
||||
onClick={props.action}
|
||||
disabled={!props.botOnline}>
|
||||
{t(props.buttonText)}
|
||||
</button>
|
||||
</Col>
|
||||
</Row>;
|
||||
};
|
|
@ -1,13 +1,14 @@
|
|||
import * as React from "react";
|
||||
import { Header } from "../hardware_settings/header";
|
||||
import { Collapse, Popover, Position } from "@blueprintjs/core";
|
||||
import { RestartRow } from "./restart_row";
|
||||
import { ShutdownRow } from "./shutdown_row";
|
||||
import { FactoryResetRow } from "./factory_reset_row";
|
||||
import { PowerAndResetProps } from "./interfaces";
|
||||
import { ChangeOwnershipForm } from "./change_ownership_form";
|
||||
import { t } from "i18next";
|
||||
import { Feature } from "../../interfaces";
|
||||
import { FbosButtonRow } from "./fbos_button_row";
|
||||
import { Content } from "../../../constants";
|
||||
import { reboot, powerOff, restartFirmware } from "../../actions";
|
||||
|
||||
export function PowerAndReset(props: PowerAndResetProps) {
|
||||
const { dispatch, sourceFbosConfig, shouldDisplay, botOnline } = props;
|
||||
|
@ -21,8 +22,28 @@ export function PowerAndReset(props: PowerAndResetProps) {
|
|||
dispatch={dispatch} />
|
||||
</div>
|
||||
<Collapse isOpen={!!power_and_reset}>
|
||||
<RestartRow botOnline={botOnline} />
|
||||
<ShutdownRow botOnline={botOnline} />
|
||||
<FbosButtonRow
|
||||
botOnline={botOnline}
|
||||
label={t("RESTART FARMBOT")}
|
||||
description={Content.RESTART_FARMBOT}
|
||||
buttonText={t("RESTART")}
|
||||
color={"yellow"}
|
||||
action={reboot} />
|
||||
<FbosButtonRow
|
||||
botOnline={botOnline}
|
||||
label={t("SHUTDOWN FARMBOT")}
|
||||
description={Content.SHUTDOWN_FARMBOT}
|
||||
buttonText={t("SHUTDOWN")}
|
||||
color={"red"}
|
||||
action={powerOff} />
|
||||
{shouldDisplay(Feature.firmware_restart) &&
|
||||
<FbosButtonRow
|
||||
botOnline={botOnline}
|
||||
label={t("RESTART FIRMWARE")}
|
||||
description={Content.RESTART_FIRMWARE}
|
||||
buttonText={t("RESTART")}
|
||||
color={"yellow"}
|
||||
action={restartFirmware} />}
|
||||
<FactoryResetRow
|
||||
dispatch={dispatch}
|
||||
sourceFbosConfig={sourceFbosConfig}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col } from "../../../ui/index";
|
||||
import { t } from "i18next";
|
||||
import { Content } from "../../../constants";
|
||||
import { reboot } from "../../actions";
|
||||
import { ColWidth } from "../farmbot_os_settings";
|
||||
|
||||
export function RestartRow({ botOnline }: { botOnline: boolean }) {
|
||||
return <Row>
|
||||
<Col xs={ColWidth.label}>
|
||||
<label>
|
||||
{t("RESTART FARMBOT")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={ColWidth.description}>
|
||||
<p>
|
||||
{t(Content.RESTART_FARMBOT)}
|
||||
</p>
|
||||
</Col>
|
||||
<Col xs={ColWidth.button}>
|
||||
<button
|
||||
className="fb-button yellow"
|
||||
type="button"
|
||||
onClick={reboot}
|
||||
disabled={!botOnline}>
|
||||
{t("RESTART")}
|
||||
</button>
|
||||
</Col>
|
||||
</Row>;
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import * as React from "react";
|
||||
import { Row, Col } from "../../../ui/index";
|
||||
import { t } from "i18next";
|
||||
import { Content } from "../../../constants";
|
||||
import { powerOff } from "../../actions";
|
||||
import { ColWidth } from "../farmbot_os_settings";
|
||||
|
||||
export function ShutdownRow({ botOnline }: { botOnline: boolean }) {
|
||||
return <Row>
|
||||
<Col xs={ColWidth.label}>
|
||||
<label>
|
||||
{t("SHUTDOWN FARMBOT")}
|
||||
</label>
|
||||
</Col>
|
||||
<Col xs={ColWidth.description}>
|
||||
<p>
|
||||
{t(Content.SHUTDOWN_FARMBOT)}
|
||||
</p>
|
||||
</Col>
|
||||
<Col xs={ColWidth.button}>
|
||||
<button
|
||||
className="fb-button red"
|
||||
type="button"
|
||||
onClick={powerOff}
|
||||
disabled={!botOnline}>
|
||||
{t("SHUTDOWN")}
|
||||
</button>
|
||||
</Col>
|
||||
</Row>;
|
||||
}
|
|
@ -65,6 +65,8 @@ export enum Feature {
|
|||
diagnostic_dumps = "diagnostic_dumps",
|
||||
rpi_led_control = "rpi_led_control",
|
||||
mark_as_step = "mark_as_step",
|
||||
firmware_restart = "firmware_restart",
|
||||
api_farmware_installations = "api_farmware_installations",
|
||||
}
|
||||
/** Object fetched from FEATURE_MIN_VERSIONS_URL. */
|
||||
export type MinOsFeatureLookup = Partial<Record<Feature, string>>;
|
||||
|
|
Loading…
Reference in New Issue