Farmbot-Web-App/frontend/devices/actions.ts

406 lines
12 KiB
TypeScript
Raw Normal View History

import axios from "axios";
2019-06-24 15:39:49 -06:00
import { success, warning, info, error } from "../toast/toast";
2017-10-10 11:59:08 -06:00
import { getDevice } from "../device";
2018-08-02 15:46:58 -06:00
import { Everything } from "../interfaces";
2018-05-01 23:25:09 -06:00
import {
GithubRelease, MoveRelProps, MinOsFeatureLookup, SourceFwConfig, Axis
} from "./interfaces";
import { Thunk } from "../redux/interfaces";
2018-08-01 18:20:50 -06:00
import {
2019-02-26 20:12:02 -07:00
McuParams, Configuration, TaggedFirmwareConfig, ParameterApplication,
2019-04-09 20:31:25 -06:00
ALLOWED_PIN_MODES,
2019-09-12 15:50:44 -06:00
FirmwareHardware
2018-08-01 18:20:50 -06:00
} from "farmbot";
import { ControlPanelState } from "../devices/interfaces";
2018-03-09 22:17:16 -07:00
import { oneOf, versionOK, trim } from "../util";
import { Actions, Content } from "../constants";
2017-08-30 09:42:12 -06:00
import { mcuParamValidator } from "./update_interceptor";
2018-01-27 02:29:13 -07:00
import { edit, save as apiSave } from "../api/crud";
import { CONFIG_DEFAULTS } from "farmbot/dist/config";
2018-10-22 09:35:44 -06:00
import { Log } from "farmbot/dist/resources/api_resources";
import { FbosConfig } from "farmbot/dist/resources/configs/fbos";
import { FirmwareConfig } from "farmbot/dist/resources/configs/firmware";
import { getFirmwareConfig, getFbosConfig } from "../resources/getters";
2019-02-04 07:32:26 -07:00
import { isObject, isString, get, noop } from "lodash";
2019-04-02 13:59:37 -06:00
import { t } from "../i18next_wrapper";
2017-06-29 12:54:02 -06:00
const ON = 1, OFF = 0;
2017-08-30 07:30:40 -06:00
export type ConfigKey = keyof McuParams;
2018-03-19 09:25:42 -06:00
export const EXPECTED_MAJOR = 6;
2017-08-08 16:09:37 -06:00
export const EXPECTED_MINOR = 0;
2018-03-07 20:42:34 -07:00
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
2019-07-16 13:07:29 -06:00
// an additional layer of safety.
const BAD_WORDS = ["WPA", "PSK", "PASSWORD", "NERVES"];
const MESSAGE: keyof Log = "message";
2017-06-29 12:54:02 -06:00
export function isLog(x: unknown): x is Log {
const msg = get(x, MESSAGE);
const yup = isObject(x) && isString(msg);
if (yup) {
if (oneOf(BAD_WORDS, msg.toUpperCase())) { // SECURITY CRITICAL CODE.
console.error("Refusing to display log: " + JSON.stringify(x));
return false;
}
return true;
} else {
return false;
}
2017-06-29 12:54:02 -06:00
}
2018-10-23 14:17:51 -06:00
/** Toast message upon request error. */
2018-05-01 18:55:41 -06:00
export const commandErr =
(noun = "Command") => () => error(t(`${noun} failed`));
2017-08-02 14:05:33 -06:00
2018-10-23 14:17:51 -06:00
/** Toast message upon request success. */
export const commandOK = (noun = "Command") => () => {
2018-05-06 03:00:02 -06:00
const msg = t(noun) + t(" request sent to device.");
2017-08-02 14:05:33 -06:00
success(msg, t("Request sent"));
};
2017-06-29 12:54:02 -06:00
2018-10-23 14:17:51 -06:00
/** Update FBOS. */
2017-06-29 12:54:02 -06:00
export function checkControllerUpdates() {
2019-07-02 11:03:16 -06:00
const noun = t("Check for Updates");
commandOK(noun)();
2017-10-10 11:59:08 -06:00
getDevice()
2017-06-29 12:54:02 -06:00
.checkUpdates()
.catch(commandErr(noun));
2017-06-29 12:54:02 -06:00
}
2018-10-23 14:17:51 -06:00
/** Shutdown FBOS. */
2017-06-29 12:54:02 -06:00
export function powerOff() {
2019-07-02 11:03:16 -06:00
const noun = t("Power Off Bot");
2017-10-10 11:59:08 -06:00
getDevice()
2017-06-29 12:54:02 -06:00
.powerOff()
.then(commandOK(noun), commandErr(noun));
}
2018-10-23 14:17:51 -06:00
/** Factory reset FBOS. */
2017-06-29 12:54:02 -06:00
export function factoryReset() {
2017-09-03 00:59:49 -06:00
if (!confirm(t(Content.FACTORY_RESET_ALERT))) {
2017-06-29 12:54:02 -06:00
return;
}
2017-10-10 11:59:08 -06:00
getDevice().resetOS();
2017-06-29 12:54:02 -06:00
}
2018-10-23 14:17:51 -06:00
/** Reboot FBOS. */
2017-06-29 12:54:02 -06:00
export function reboot() {
2019-07-02 11:03:16 -06:00
const noun = t("Reboot Bot");
2017-10-10 11:59:08 -06:00
getDevice()
2017-06-29 12:54:02 -06:00
.reboot()
.then(commandOK(noun), commandErr(noun));
}
2018-10-23 14:17:51 -06:00
/** Restart Farmduino firmware serial connection. */
2018-09-24 13:34:38 -06:00
export function restartFirmware() {
2019-07-02 11:03:16 -06:00
const noun = t("Restart Firmware");
2018-10-23 14:17:51 -06:00
getDevice()
.rebootFirmware()
2018-09-24 13:34:38 -06:00
.then(commandOK(noun), commandErr(noun));
}
2019-04-09 20:31:25 -06:00
export function flashFirmware(firmwareName: FirmwareHardware) {
2019-07-02 11:03:16 -06:00
const noun = t("Flash Firmware");
2019-04-09 20:31:25 -06:00
getDevice()
.flashFirmware(firmwareName)
.then(commandOK(noun), commandErr(noun));
}
2017-06-29 12:54:02 -06:00
export function emergencyLock() {
2019-07-02 11:03:16 -06:00
const noun = t("Emergency stop");
2017-10-10 11:59:08 -06:00
getDevice()
2017-06-29 12:54:02 -06:00
.emergencyLock()
.then(commandOK(noun), commandErr(noun));
}
2017-07-03 12:42:46 -06:00
2019-04-17 13:31:18 -06:00
export function emergencyUnlock(force = false) {
2019-07-02 11:03:16 -06:00
const noun = t("Emergency unlock");
2019-04-17 13:31:18 -06:00
if (force || confirm(t(`Are you sure you want to unlock the device?`))) {
2017-10-10 11:59:08 -06:00
getDevice()
.emergencyUnlock()
.then(commandOK(noun), commandErr(noun));
2017-06-29 12:54:02 -06:00
}
}
export function sync(): Thunk {
2019-07-02 11:03:16 -06:00
const noun = t("Sync");
return function (_dispatch, getState) {
2018-10-23 14:17:51 -06:00
const currentFBOSversion =
getState().bot.hardware.informational_settings.controller_version;
const IS_OK = versionOK(currentFBOSversion, EXPECTED_MAJOR, EXPECTED_MINOR);
2017-06-29 12:54:02 -06:00
if (IS_OK) {
2017-10-10 11:59:08 -06:00
getDevice()
2017-06-29 12:54:02 -06:00
.sync()
.catch(commandErr(noun));
2017-06-29 12:54:02 -06:00
} else {
2018-10-23 14:17:51 -06:00
if (currentFBOSversion) {
badVersion();
} else {
info(t("FarmBot is not connected."), t("Disconnected"), "red");
}
2017-06-29 12:54:02 -06:00
}
};
}
2019-01-12 05:25:02 -07:00
export function execSequence(
sequenceId: number | undefined,
2019-02-22 19:09:40 -07:00
bodyVariables?: ParameterApplication[]
2019-01-12 05:25:02 -07:00
) {
2019-07-02 11:03:16 -06:00
const noun = t("Sequence execution");
2019-01-12 05:25:02 -07:00
if (sequenceId) {
2017-12-11 20:52:51 -07:00
commandOK(noun)();
return getDevice()
.execSequence(sequenceId, bodyVariables)
.catch((x: Error) => {
if (x && (typeof x == "object") && (typeof x.message == "string")) {
error(x.message);
} else {
2019-09-18 07:36:33 -06:00
commandErr(noun)();
}
});
2017-06-29 12:54:02 -06:00
} else {
2018-07-01 00:42:37 -06:00
throw new Error(t("Can't execute unsaved sequences"));
2017-06-29 12:54:02 -06:00
}
}
export function requestDiagnostic() {
2019-07-02 11:03:16 -06:00
const noun = t("Diagnostic Request");
return getDevice().dumpInfo().then(commandOK(noun), commandErr(noun));
}
2019-01-04 14:26:52 -07:00
const tagNameToVersionString = (tagName: string): string =>
tagName.toLowerCase().replace("v", "");
/**
* Fetch FarmBot OS beta release data.
* Ignores a specific release provided by the url (i.e., `releases/1234`)
* in favor of the latest `-beta` release.
*/
export const fetchLatestGHBetaRelease =
(url: string) =>
(dispatch: Function) => {
const urlArray = url.split("/");
const releasesURL = urlArray.splice(0, urlArray.length - 1).join("/");
axios
.get<GithubRelease[]>(releasesURL)
.then(resp => {
2019-07-10 16:45:58 -06:00
const latestBeta = resp.data.filter(x =>
x.tag_name.includes("beta") || x.tag_name.includes("rc"))[0];
2019-01-04 14:26:52 -07:00
const { tag_name, target_commitish } = latestBeta;
const version = tagNameToVersionString(tag_name);
dispatch({
type: Actions.FETCH_BETA_OS_UPDATE_INFO_OK,
payload: { version, commit: target_commitish }
});
})
.catch(ferror => dispatch({
type: "FETCH_BETA_OS_UPDATE_INFO_ERROR",
payload: ferror
}));
};
2018-10-23 14:17:51 -06:00
/** Fetch FarmBot OS release data. */
export const fetchReleases =
2018-01-10 17:37:36 -07:00
(url: string, options = { beta: false }) =>
(dispatch: Function) => {
2018-01-10 17:37:36 -07:00
axios
2018-03-09 22:17:16 -07:00
.get<GithubRelease>(url)
.then(resp => {
2018-01-28 14:00:16 -07:00
const { tag_name, target_commitish } = resp.data;
2019-01-04 14:26:52 -07:00
const version = tagNameToVersionString(tag_name);
2018-01-10 17:37:36 -07:00
dispatch({
type: options.beta
? Actions.FETCH_BETA_OS_UPDATE_INFO_OK
: Actions.FETCH_OS_UPDATE_INFO_OK,
2018-01-28 14:00:16 -07:00
payload: { version, commit: target_commitish }
2018-01-10 17:37:36 -07:00
});
})
.catch((ferror) => {
2018-01-12 12:54:37 -07:00
!options.beta &&
error(t("Could not download FarmBot OS update information."));
2018-01-10 17:37:36 -07:00
dispatch({
type: options.beta
? "FETCH_BETA_OS_UPDATE_INFO_ERROR"
: "FETCH_OS_UPDATE_INFO_ERROR",
payload: ferror
});
2017-06-29 12:54:02 -06:00
});
2018-01-10 17:37:36 -07:00
};
2017-06-29 12:54:02 -06:00
2018-03-08 18:02:50 -07:00
/**
* Structure and type checks for fetched minimum FBOS version feature object.
* @param x axios response data
*/
function validMinOsFeatureLookup(x: MinOsFeatureLookup): boolean {
2019-02-04 07:32:26 -07:00
return isObject(x) &&
2018-03-08 18:02:50 -07:00
Object.entries(x).every(([key, val]) =>
typeof key === "string" && // feature name
typeof val === "string" && // version string
val.split(".").length > 2); // "0.0.0"
}
/**
* Fetch and save minimum FBOS version data for UI feature display.
* @param url location of data
*/
2018-03-07 20:42:34 -07:00
export let fetchMinOsFeatureData = (url: string) =>
(dispatch: Function) => {
2018-03-07 20:42:34 -07:00
axios
2018-03-09 22:17:16 -07:00
.get<MinOsFeatureLookup>(url)
.then(resp => {
2018-03-08 18:02:50 -07:00
const data = resp.data;
if (validMinOsFeatureLookup(data)) {
dispatch({
type: Actions.FETCH_MIN_OS_FEATURE_INFO_OK,
payload: data
});
} else {
console.log(`Warning! Got '${JSON.stringify(data)}', ` +
"expected min OS feature data.");
}
2018-03-07 20:42:34 -07:00
})
.catch((ferror) => {
dispatch({
type: Actions.FETCH_MIN_OS_FEATURE_INFO_ERROR,
2018-03-07 20:42:34 -07:00
payload: ferror
});
});
};
2017-06-29 12:54:02 -06:00
/**
* Toggles visibility of individual sections in the giant controls panel
* found on the Devices page.
*/
export function toggleControlPanel(payload: keyof ControlPanelState) {
return { type: Actions.TOGGLE_CONTROL_PANEL_OPTION, payload };
2017-06-29 12:54:02 -06:00
}
2018-10-23 14:17:51 -06:00
/** Toggle visibility of all hardware control panel sections. */
export function bulkToggleControlPanel(payload: boolean) {
return { type: Actions.BULK_TOGGLE_CONTROL_PANEL, payload };
}
2018-10-23 14:17:51 -06:00
/** Factory reset all firmware settings. */
2017-06-29 12:54:02 -06:00
export function MCUFactoryReset() {
2017-12-11 00:06:20 -07:00
if (!confirm(t(Content.MCU_RESET_ALERT))) {
return;
}
return getDevice().resetMCU().catch(commandErr("MCU Reset"));
2017-06-29 12:54:02 -06:00
}
2018-10-23 14:17:51 -06:00
/** Toggle a firmware setting. */
export function settingToggle(
2018-03-09 02:34:24 -07:00
name: ConfigKey,
sourceFwConfig: SourceFwConfig,
displayAlert?: string | undefined
) {
2018-03-09 02:34:24 -07:00
return function (dispatch: Function, getState: () => Everything) {
if (displayAlert) { alert(trim(displayAlert)); }
const update = { [name]: (sourceFwConfig(name).value === 0) ? ON : OFF };
const firmwareConfig = getFirmwareConfig(getState().resources.index);
const toggleFirmwareConfig = (fwConfig: TaggedFirmwareConfig) => {
dispatch(edit(fwConfig, update));
dispatch(apiSave(fwConfig.uuid));
};
2019-02-26 20:12:02 -07:00
if (firmwareConfig) {
2018-03-09 02:34:24 -07:00
return toggleFirmwareConfig(firmwareConfig);
}
};
}
2017-06-29 12:54:02 -06:00
export function moveRelative(props: MoveRelProps) {
2017-10-10 11:59:08 -06:00
return getDevice()
2017-06-29 12:54:02 -06:00
.moveRelative(props)
2019-02-04 07:32:26 -07:00
.then(noop, commandErr("Relative movement"));
2017-06-29 12:54:02 -06:00
}
export function moveAbs(props: MoveRelProps) {
2019-07-02 11:03:16 -06:00
const noun = t("Absolute movement");
2017-10-10 11:59:08 -06:00
return getDevice()
2017-06-29 12:54:02 -06:00
.moveAbsolute(props)
2019-02-04 07:32:26 -07:00
.then(noop, commandErr(noun));
2017-06-29 12:54:02 -06:00
}
export function pinToggle(pin_number: number) {
2019-07-02 11:03:16 -06:00
const noun = t("Setting toggle");
2017-10-10 11:59:08 -06:00
return getDevice()
2017-06-29 12:54:02 -06:00
.togglePin({ pin_number })
2019-02-04 07:32:26 -07:00
.then(noop, commandErr(noun));
2017-06-29 12:54:02 -06:00
}
2019-06-12 16:16:32 -06:00
export function readPin(
pin_number: number, label: string, pin_mode: ALLOWED_PIN_MODES
) {
2019-07-02 11:03:16 -06:00
const noun = t("Read pin");
2018-03-10 00:17:53 -07:00
return getDevice()
2018-09-24 13:34:38 -06:00
.readPin({ pin_number, label, pin_mode })
2019-02-04 07:32:26 -07:00
.then(noop, commandErr(noun));
2018-03-10 00:17:53 -07:00
}
2017-06-29 12:54:02 -06:00
export function homeAll(speed: number) {
2019-07-02 11:03:16 -06:00
const noun = t("'Home All' command");
2017-10-10 11:59:08 -06:00
getDevice()
2017-06-29 12:54:02 -06:00
.home({ axis: "all", speed })
2017-12-15 01:41:56 -07:00
.catch(commandErr(noun));
2017-06-29 12:54:02 -06:00
}
export function findHome(axis: Axis, speed = CONFIG_DEFAULTS.speed) {
2019-07-02 11:03:16 -06:00
const noun = t("'Find Home' command");
2018-05-01 23:25:09 -06:00
getDevice()
.findHome({ axis, speed })
.catch(commandErr(noun));
}
2018-10-23 14:17:51 -06:00
/** Update firmware setting. */
2017-08-30 07:30:40 -06:00
export function updateMCU(key: ConfigKey, val: string) {
2017-08-30 09:42:12 -06:00
return function (dispatch: Function, getState: () => Everything) {
2018-03-09 02:34:24 -07:00
const firmwareConfig = getFirmwareConfig(getState().resources.index);
const getParams = () => {
2019-02-26 20:12:02 -07:00
if (firmwareConfig) {
2018-03-09 02:34:24 -07:00
return firmwareConfig.body;
} else {
return getState().bot.hardware.mcu_params;
}
};
2017-08-30 10:41:40 -06:00
2017-08-30 09:42:12 -06:00
function proceed() {
2019-02-26 20:12:02 -07:00
if (firmwareConfig) {
2018-03-09 02:34:24 -07:00
dispatch(edit(firmwareConfig, { [key]: val } as Partial<FirmwareConfig>));
dispatch(apiSave(firmwareConfig.uuid));
}
2017-08-30 09:42:12 -06:00
}
2017-08-30 10:41:40 -06:00
const dont = (err: string) => warning(err);
2017-08-30 09:42:12 -06:00
2018-03-09 02:34:24 -07:00
const validate = mcuParamValidator(key, parseInt(val, 10), getParams());
2017-08-30 09:42:12 -06:00
validate(proceed, dont);
};
2017-06-29 12:54:02 -06:00
}
2018-10-23 14:17:51 -06:00
/** Update FBOS setting. */
2017-06-29 12:54:02 -06:00
export function updateConfig(config: Configuration) {
2018-01-28 20:25:19 -07:00
return function (dispatch: Function, getState: () => Everything) {
const fbosConfig = getFbosConfig(getState().resources.index);
2019-02-26 20:12:02 -07:00
if (fbosConfig) {
2018-03-09 02:34:24 -07:00
dispatch(edit(fbosConfig, config as Partial<FbosConfig>));
2018-01-28 20:25:19 -07:00
dispatch(apiSave(fbosConfig.uuid));
2018-01-27 02:29:13 -07:00
}
};
2017-06-29 12:54:02 -06:00
}
2018-10-23 14:17:51 -06:00
/** Change jog button movement amount. */
2017-06-29 12:54:02 -06:00
export function changeStepSize(integer: number) {
return {
2017-11-01 08:11:18 -06:00
type: Actions.CHANGE_STEP_SIZE,
2017-06-29 12:54:02 -06:00
payload: integer
};
}
export function badVersion() {
2019-09-23 12:56:35 -06:00
info(t("You are running an old version of FarmBot OS."),
t("Please Update"), "red");
2017-06-29 12:54:02 -06:00
}