allow long int for axis length

pull/583/head
gabrielburnworth 2017-12-16 16:16:19 -08:00
parent 5aa2e7b9ee
commit 6c0397c441
8 changed files with 122 additions and 32 deletions

View File

@ -14,7 +14,8 @@ import {
move,
shortRevision,
clampUnsignedInteger,
isUndefined
isUndefined,
IntegerSize
} from "../util";
describe("util", () => {
describe("safeStringFetch", () => {
@ -233,17 +234,22 @@ describe("shortRevision()", () => {
describe("clampUnsignedInteger()", () => {
function clampTest(
input: string, output: number | undefined, message: string) {
it(message, () => {
const result = clampUnsignedInteger(input);
input: string,
output: number | undefined,
message: string,
size: IntegerSize) {
it(`${size}: ${message}`, () => {
const result = clampUnsignedInteger(input, size);
expect(result.outcome).toEqual(message);
expect(result.result).toEqual(output);
});
}
clampTest("nope", undefined, "malformed");
clampTest("100000", 32000, "high");
clampTest("-100000", 0, "low");
clampTest("1000", 1000, "ok");
clampTest("nope", undefined, "malformed", "short");
clampTest("100000", 32000, "high", "short");
clampTest("-100000", 0, "low", "short");
clampTest("1000", 1000, "ok", "short");
clampTest("1000000", 1000000, "ok", "long");
clampTest("-1000000", 0, "low", "long");
});
describe("isUndefined()", () => {

View File

@ -0,0 +1,50 @@
jest.mock("../../../actions", () => ({
updateMCU: jest.fn()
}));
const mockWarn = jest.fn();
jest.mock("farmbot-toastr", () => ({ warning: mockWarn }));
import * as React from "react";
import { mount } from "enzyme";
import { HomingAndCalibration } from "../homing_and_calibration";
import { bot } from "../../../../__test_support__/fake_state/bot";
import { updateMCU } from "../../../actions";
describe("<HomingAndCalibration />", () => {
beforeEach(function () {
jest.clearAllMocks();
});
function testAxisLengthInput(
fw: string, provided: string, expected: string) {
const dispatch = jest.fn();
bot.controlPanelState.homing_and_calibration = true;
bot.hardware.informational_settings.firmware_version = fw;
const result = mount(<HomingAndCalibration
dispatch={dispatch} bot={bot} />);
const e = { currentTarget: { value: provided } } as
React.SyntheticEvent<HTMLInputElement>;
const input = result.find("input").first().props();
input.onChange && input.onChange(e);
input.onSubmit && input.onSubmit(e);
expect(updateMCU)
.toHaveBeenCalledWith("movement_axis_nr_steps_x", expected);
}
it("short int", () => {
testAxisLengthInput("5.0.0", "100000", "32000");
expect(mockWarn)
.toHaveBeenCalledWith("Maximum input is 32,000. Rounding down.");
});
it("long int: too long", () => {
testAxisLengthInput("6.0.0", "10000000000", "2000000000");
expect(mockWarn)
.toHaveBeenCalledWith("Maximum input is 2,000,000,000. Rounding down.");
});
it("long int: ok", () => {
testAxisLengthInput("6.0.0", "100000", "100000");
expect(mockWarn).not.toHaveBeenCalled();
});
});

View File

@ -10,13 +10,20 @@ import { enabledAxisMap } from "../axis_tracking_status";
import { HomingAndCalibrationProps } from "../interfaces";
import { Header } from "./header";
import { Collapse } from "@blueprintjs/core";
import { minFwVersionCheck } from "../../../util";
export function HomingAndCalibration(props: HomingAndCalibrationProps) {
const { dispatch, bot } = props;
const { mcu_params } = bot.hardware;
const { firmware_version } = bot.hardware.informational_settings;
const { homing_and_calibration } = props.bot.controlPanelState;
const axisLengthIntSize =
minFwVersionCheck(firmware_version, "6.0.0")
? "long"
: "short";
/**
* Tells us if X/Y/Z have a means of checking their position.
* FARMBOT WILL CRASH INTO WALLS IF THIS IS WRONG! BE CAREFUL.
@ -76,7 +83,8 @@ export function HomingAndCalibration(props: HomingAndCalibrationProps) {
y={"movement_axis_nr_steps_y"}
z={"movement_axis_nr_steps_z"}
bot={bot}
dispatch={dispatch} />
dispatch={dispatch}
intSize={axisLengthIntSize} />
<NumericMCUInputGroup
name={t("Timeout after (seconds)")}
tooltip={t(ToolTips.TIMEOUT_AFTER)}

View File

@ -1,5 +1,6 @@
import { BotState } from "../interfaces";
import { McuParamName, McuParams } from "farmbot/dist";
import { IntegerSize } from "../../util";
export interface HomingRowProps {
hardware: McuParams;
@ -37,6 +38,7 @@ export interface NumericMCUInputGroupProps {
x: McuParamName;
y: McuParamName;
z: McuParamName;
intSize?: IntegerSize;
}
export interface MotorsProps {

View File

@ -4,7 +4,7 @@ import { warning } from "farmbot-toastr";
import { McuInputBoxProps } from "../interfaces";
import { updateMCU } from "../actions";
import { BlurableInput } from "../../ui/index";
import { clampUnsignedInteger } from "../../util";
import { clampUnsignedInteger, IntegerSize } from "../../util";
import { t } from "i18next";
export class McuInputBox extends React.Component<McuInputBoxProps, {}> {
@ -16,25 +16,31 @@ export class McuInputBox extends React.Component<McuInputBoxProps, {}> {
return _.isUndefined(v) ? "" : (v || 0).toString();
}
clampInputAndWarn = (input: string, intSize: IntegerSize): number => {
const result = clampUnsignedInteger(input, intSize);
const max = intSize === "long" ? "2,000,000,000" : "32,000";
switch (result.outcome) {
case "ok":
break;
case "high":
warning(t(`Maximum input is ${max}. Rounding down.`));
break;
case "low":
warning(t("Must be a positive number. Rounding up to 0."));
break;
default:
warning(t(`Please enter a number between 0 and ${max}`));
throw new Error("Bad input in mcu_input_box. Impossible?");
}
return result.result;
}
commit = (e: React.SyntheticEvent<HTMLInputElement>) => {
const { value } = e.currentTarget;
const actuallyDifferent = this.value !== value;
if (actuallyDifferent) {
const result = clampUnsignedInteger(value);
switch (result.outcome) {
case "ok":
break;
case "high":
warning(t("Maximum input is 32,000. Rounding down."));
break;
case "low":
warning(t("Must be a positive number. Rounding up to 0."));
break;
default:
warning(t("Please enter a number between 0 and 32,000"));
throw new Error("Bad input in mcu_input_box. Impossible?");
}
this.props.dispatch(updateMCU(this.key, result.result.toString()));
const result = this.clampInputAndWarn(value, this.props.intSize);
this.props.dispatch(updateMCU(this.key, result.toString()));
}
}

View File

@ -7,7 +7,7 @@ import { Col } from "../../ui/index";
export function NumericMCUInputGroup(props: NumericMCUInputGroupProps) {
const { bot, dispatch, tooltip, name, x, y, z } = props;
const { bot, dispatch, tooltip, name, x, y, z, intSize } = props;
return <Row>
<Col xs={6}>
<label>
@ -19,19 +19,22 @@ export function NumericMCUInputGroup(props: NumericMCUInputGroupProps) {
<McuInputBox
setting={x}
bot={bot}
dispatch={dispatch} />
dispatch={dispatch}
intSize={intSize} />
</Col>
<Col xs={2}>
<McuInputBox
setting={y}
bot={bot}
dispatch={dispatch} />
dispatch={dispatch}
intSize={intSize} />
</Col>
<Col xs={2}>
<McuInputBox
setting={z}
bot={bot}
dispatch={dispatch} />
dispatch={dispatch}
intSize={intSize} />
</Col>
</Row>;
}

View File

@ -18,6 +18,7 @@ import { TaggedUser } from "../resources/tagged_resources";
import { WD_ENV } from "../farmware/weed_detector/remote_env/interfaces";
import { EncoderDisplay } from "../controls/interfaces";
import { ConnectionStatus } from "../connectivity/interfaces";
import { IntegerSize } from "../util";
export interface Props {
userToApi: ConnectionStatus | undefined;
@ -128,6 +129,7 @@ export interface McuInputBoxProps {
bot: BotState;
setting: McuParamName;
dispatch: Function;
intSize?: IntegerSize;
}
export interface EStopButtonProps {

View File

@ -323,7 +323,8 @@ export function attachToRoot<P>(type: React.ComponentClass<P>,
}
/** The firmware will have an integer overflow if you don't check this one. */
const MAX_INPUT = 32000;
const MAX_SHORT_INPUT = 32000;
const MAX_LONG_INPUT = 2000000000;
const MIN_INPUT = 0;
interface High { outcome: "high"; result: number; }
@ -332,14 +333,26 @@ interface Malformed { outcome: "malformed"; result: undefined; }
interface Ok { outcome: "ok", result: number; }
export type ClampResult = High | Low | Malformed | Ok;
export type IntegerSize = "short" | "long" | undefined;
/** Handle all the possible ways a user could give us bad data or cause an
* integer overflow in the firmware. */
export function clampUnsignedInteger(input: string): ClampResult {
export function clampUnsignedInteger(
input: string, size: IntegerSize): ClampResult {
const result = Math.round(parseInt(input, 10));
const maxInput = () => {
switch (size) {
case "long":
return MAX_LONG_INPUT;
case "short":
default:
return MAX_SHORT_INPUT;
}
};
// Clamp to prevent overflow.
if (_.isNaN(result)) { return { outcome: "malformed", result: undefined }; }
if (result > MAX_INPUT) { return { outcome: "high", result: MAX_INPUT }; }
if (result > maxInput()) { return { outcome: "high", result: maxInput() }; }
if (result < MIN_INPUT) { return { outcome: "low", result: MIN_INPUT }; }
return { outcome: "ok", result };