sensors UI

pull/712/head
gabrielburnworth 2018-03-09 23:17:53 -08:00
parent 32f8552c98
commit ac698edb73
18 changed files with 617 additions and 11 deletions

View File

@ -8,6 +8,7 @@ import "../config/interfaces";
import "../connectivity/interfaces";
import "../controls/interfaces";
import "../controls/peripherals/interfaces";
import "../controls/sensors/interfaces";
import "../controls/webcam/interfaces";
import "../devices/components/interfaces";
import "../devices/components/fbos_settings/interfaces";

View File

@ -123,8 +123,8 @@ export class API {
get firmwareConfigPath() { return `${this.baseUrl}/api/firmware_config/`; }
/** /api/sensor_readings */
get sensorReadingPath() { return `${this.baseUrl}/api/sensor_readings`; }
/** /api/sensor_readings */
get sensorPath() { return `${this.baseUrl}/api/sensors`; }
/** /api/sensors/ */
get sensorPath() { return `${this.baseUrl}/api/sensors/`; }
/** /api/device_configs/:id */
get deviceConfigPath() { return `${this.baseUrl}/api/device_configs`; }
/** /api/pin_bindings/:id */

View File

@ -212,6 +212,7 @@ export function urlFor(tag: ResourceName) {
FarmEvent: API.current.farmEventsPath,
Regimen: API.current.regimensPath,
Peripheral: API.current.peripheralsPath,
Sensor: API.current.sensorPath,
Point: API.current.pointsPath,
User: API.current.usersPath,
Device: API.current.devicePath,

View File

@ -18,6 +18,10 @@ export namespace ToolTips {
To edit and create new peripherals, press the EDIT button. Make sure to turn
things off when you're done!`);
export const SENSORS =
trim(`Add sensors here to monitor FarmBot's sensors.
To edit and create new sensors, press the EDIT button.`);
// Device
export const OS_SETTINGS =
trim(`View and change device settings.`);

View File

@ -19,7 +19,9 @@ import * as React from "react";
import { mount } from "enzyme";
import { Controls } from "../controls";
import { bot } from "../../__test_support__/fake_state/bot";
import { fakePeripheral, fakeWebcamFeed } from "../../__test_support__/fake_state/resources";
import {
fakePeripheral, fakeWebcamFeed, fakeSensor
} from "../../__test_support__/fake_state/resources";
import { Dictionary } from "farmbot";
import { BooleanSetting } from "../../session_keys";
import { Props } from "../interfaces";
@ -32,8 +34,10 @@ describe("<Controls />", () => {
feeds: [fakeWebcamFeed()],
user: undefined,
peripherals: [fakePeripheral()],
sensors: [fakeSensor()],
botToMqttStatus: "up",
firmwareSettings: bot.hardware.mcu_params,
shouldDisplay: x => true,
};
}
@ -41,7 +45,7 @@ describe("<Controls />", () => {
mockStorj[BooleanSetting.hide_webcam_widget] = false;
const wrapper = mount(<Controls {...fakeProps()} />);
const txt = wrapper.text().toLowerCase();
["webcam", "move", "peripherals"]
["webcam", "move", "peripherals", "sensors"]
.map(string => expect(txt).toContain(string));
});
@ -49,7 +53,16 @@ describe("<Controls />", () => {
mockStorj[BooleanSetting.hide_webcam_widget] = true;
const wrapper = mount(<Controls {...fakeProps()} />);
const txt = wrapper.text().toLowerCase();
["move", "peripherals"].map(string => expect(txt).toContain(string));
["move", "peripherals", "sensors"]
.map(string => expect(txt).toContain(string));
expect(txt).not.toContain("webcam");
});
it("doesn't show sensors", () => {
const p = fakeProps();
p.shouldDisplay = x => false;
const wrapper = mount(<Controls {...p} />);
const txt = wrapper.text().toLowerCase();
expect(txt).not.toContain("sensors");
});
});

View File

@ -1,6 +1,7 @@
import * as React from "react";
import { connect } from "react-redux";
import { Peripherals } from "./peripherals";
import { Sensors } from "./sensors";
import { Row, Page, Col } from "../ui/index";
import { mapStateToProps } from "./state_to_props";
import { WebcamPanel } from "./webcam";
@ -9,6 +10,7 @@ import { Move } from "./move";
import { BooleanSetting } from "../session_keys";
import { Session } from "../session";
import { catchErrors } from "../util";
import { Feature } from "../devices/interfaces";
@connect(mapStateToProps)
export class Controls extends React.Component<Props, {}> {
@ -48,6 +50,12 @@ export class Controls extends React.Component<Props, {}> {
</Col>
<Col xs={12} sm={6}>
<WebcamPanel feeds={this.props.feeds} dispatch={this.props.dispatch} />
{this.props.shouldDisplay(Feature.sensors) &&
<Sensors
bot={this.props.bot}
sensors={this.props.sensors}
dispatch={this.props.dispatch}
disabled={arduinoBusy} />}
</Col>
</Row>
:
@ -61,6 +69,12 @@ export class Controls extends React.Component<Props, {}> {
peripherals={this.props.peripherals}
dispatch={this.props.dispatch}
disabled={arduinoBusy} />
{this.props.shouldDisplay(Feature.sensors) &&
<Sensors
bot={this.props.bot}
sensors={this.props.sensors}
dispatch={this.props.dispatch}
disabled={arduinoBusy} />}
</Col>
</Row>}
</Page>;

View File

@ -1,9 +1,10 @@
import { BotState, Xyz, BotPosition } from "../devices/interfaces";
import { BotState, Xyz, BotPosition, ShouldDisplay } from "../devices/interfaces";
import { Vector3, McuParams } from "farmbot/dist";
import {
TaggedUser,
TaggedWebcamFeed,
TaggedPeripheral
TaggedPeripheral,
TaggedSensor
} from "../resources/tagged_resources";
import { NetworkState } from "../connectivity/interfaces";
@ -13,8 +14,10 @@ export interface Props {
feeds: TaggedWebcamFeed[];
user: TaggedUser | undefined;
peripherals: TaggedPeripheral[];
sensors: TaggedSensor[];
botToMqttStatus: NetworkState;
firmwareSettings: McuParams;
shouldDisplay: ShouldDisplay;
}
export interface MoveProps {

View File

@ -0,0 +1,89 @@
const mockError = jest.fn();
jest.mock("farmbot-toastr", () => ({
error: mockError
}));
import * as React from "react";
import { mount } from "enzyme";
import { Sensors } from "../index";
import { bot } from "../../../__test_support__/fake_state/bot";
import { SensorsProps } from "../../../devices/interfaces";
import { fakeSensor } from "../../../__test_support__/fake_state/resources";
describe("<Sensors />", () => {
beforeEach(function () {
jest.clearAllMocks();
});
function fakeProps(): SensorsProps {
const fakeSensor1 = fakeSensor();
const fakeSensor2 = fakeSensor();
fakeSensor1.body.pin = 1;
fakeSensor2.body.pin = 2;
return {
bot,
sensors: [fakeSensor1, fakeSensor2],
dispatch: jest.fn(),
disabled: false
};
}
it("renders", () => {
const wrapper = mount(<Sensors {...fakeProps()} />);
["Sensors", "Edit", "Save", "Fake Pin", "1"].map(string =>
expect(wrapper.text()).toContain(string));
const saveButton = wrapper.find("button").at(1);
expect(saveButton.text()).toContain("Save");
expect(saveButton.props().hidden).toBeTruthy();
});
it("isEditing", () => {
const wrapper = mount(<Sensors {...fakeProps()} />);
expect(wrapper.state().isEditing).toBeFalsy();
const edit = wrapper.find("button").at(0);
expect(edit.text()).toEqual("Edit");
edit.simulate("click");
expect(wrapper.state().isEditing).toBeTruthy();
});
it("save attempt: pin number too small", () => {
const p = fakeProps();
p.sensors[0].body.pin = 1;
p.sensors[1].body.pin = 1;
const wrapper = mount(<Sensors {...p} />);
const save = wrapper.find("button").at(1);
expect(save.text()).toContain("Save");
save.simulate("click");
expect(mockError).toHaveBeenLastCalledWith("Pin numbers must be unique.");
});
it("saves", () => {
const p = fakeProps();
p.sensors[0].body.pin = 1;
const wrapper = mount(<Sensors {...p} />);
const save = wrapper.find("button").at(1);
expect(save.text()).toContain("Save");
save.simulate("click");
expect(p.dispatch).toHaveBeenCalled();
});
it("adds empty sensor", () => {
const p = fakeProps();
const wrapper = mount(<Sensors {...p} />);
wrapper.setState({ isEditing: true });
const add = wrapper.find("button").at(2);
expect(add.text()).toEqual("");
add.simulate("click");
expect(p.dispatch).toHaveBeenCalled();
});
it("adds stock sensors", () => {
const p = fakeProps();
const wrapper = mount(<Sensors {...p} />);
wrapper.setState({ isEditing: true });
const add = wrapper.find("button").at(3);
expect(add.text()).toEqual("Stock sensors");
add.simulate("click");
expect(p.dispatch).toHaveBeenCalledTimes(2);
});
});

View File

@ -0,0 +1,73 @@
import * as React from "react";
import { shallow } from "enzyme";
import { SensorForm } from "../sensor_form";
import { Actions } from "../../../constants";
import { SensorFormProps } from "../interfaces";
import { fakeSensor } from "../../../__test_support__/fake_state/resources";
describe("<SensorForm/>", function () {
beforeEach(() => {
jest.clearAllMocks();
});
const fakeProps = (): SensorFormProps => {
const fakeSensor1 = fakeSensor();
const fakeSensor2 = fakeSensor();
fakeSensor1.body.id = 1;
fakeSensor1.body.pin = 51;
fakeSensor1.body.label = "GPIO 51";
fakeSensor2.body.id = 2;
fakeSensor2.body.pin = 50;
fakeSensor2.body.label = "GPIO 50 - Moisture";
return {
dispatch: jest.fn(),
sensors: [fakeSensor2, fakeSensor1]
};
};
const expectedPayload = (update: Object) =>
expect.objectContaining({
payload: expect.objectContaining({
update
}),
type: Actions.EDIT_RESOURCE
});
it("renders a list of editable sensors, in sorted order", () => {
const form = shallow(<SensorForm {...fakeProps()} />);
const inputs = form.find("input");
expect(inputs.at(0).props().value).toEqual("GPIO 51");
expect(inputs.at(1).props().value).toEqual("GPIO 50 - Moisture");
});
it("updates label", () => {
const p = fakeProps();
const form = shallow(<SensorForm {...p} />);
const inputs = form.find("input");
inputs.at(0).simulate("change", { currentTarget: { value: "GPIO 52" } });
expect(p.dispatch).toHaveBeenCalledWith(
expectedPayload({ label: "GPIO 52" }));
});
it("updates pin", () => {
const p = fakeProps();
const form = shallow(<SensorForm {...p} />);
form.find("FBSelect").at(0).simulate("change", { value: 52 });
expect(p.dispatch).toHaveBeenCalledWith(expectedPayload({ pin: 52 }));
});
it("updates mode", () => {
const p = fakeProps();
const form = shallow(<SensorForm {...p} />);
form.find("FBSelect").at(1).simulate("change", { value: 0 });
expect(p.dispatch).toHaveBeenCalledWith(expectedPayload({ mode: 0 }));
});
it("deletes sensor", () => {
const p = fakeProps();
const form = shallow(<SensorForm {...p} />);
const buttons = form.find("button");
buttons.at(0).simulate("click");
expect(p.dispatch).toHaveBeenCalledWith(expect.any(Function));
});
});

View File

@ -0,0 +1,94 @@
const mockDevice = {
send: jest.fn(() => { return Promise.resolve(); })
};
jest.mock("../../../device", () => ({
getDevice: () => (mockDevice)
}));
import * as React from "react";
import { mount } from "enzyme";
import { SensorList } from "../sensor_list";
import { Pins } from "farmbot/dist";
import { fakeSensor } from "../../../__test_support__/fake_state/resources";
import { SensorListProps } from "../interfaces";
describe("<SensorList/>", function () {
beforeEach(() => {
jest.clearAllMocks();
});
const fakeProps = (): SensorListProps => {
const pins: Pins = {
50: {
mode: 0,
value: 1
},
51: {
mode: 1,
value: 500
}
};
const fakeSensor1 = fakeSensor();
const fakeSensor2 = fakeSensor();
fakeSensor1.body.id = 1;
fakeSensor1.body.pin = 51;
fakeSensor1.body.mode = 1;
fakeSensor1.body.label = "GPIO 51";
fakeSensor2.body.id = 2;
fakeSensor2.body.pin = 50;
fakeSensor2.body.mode = 0;
fakeSensor2.body.label = "GPIO 50 - Moisture";
return {
dispatch: jest.fn(),
sensors: [fakeSensor2, fakeSensor1],
pins,
disabled: false
};
};
it("renders a list of sensors, in sorted order", function () {
const wrapper = mount(<SensorList {...fakeProps()} />);
const labels = wrapper.find("label");
const pinNumbers = wrapper.find("p");
expect(labels.first().text()).toEqual("GPIO 51");
expect(pinNumbers.first().text()).toEqual("51");
expect(wrapper.find(".indicator").first().text()).toEqual("500");
expect(labels.last().text()).toEqual("GPIO 50 - Moisture");
expect(pinNumbers.last().text()).toEqual("50");
expect(wrapper.find(".indicator").last().text()).toEqual("1");
});
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
}
})]
});
it("reads pins", () => {
const wrapper = mount(<SensorList {...fakeProps()} />);
const toggle = wrapper.find("button");
toggle.first().simulate("click");
expect(mockDevice.send).toHaveBeenCalledWith(expectedPayload(51, 1));
toggle.last().simulate("click");
expect(mockDevice.send).toHaveBeenLastCalledWith(expectedPayload(50, 0));
expect(mockDevice.send).toHaveBeenCalledTimes(2);
});
it("pins toggles are disabled", () => {
const p = fakeProps();
p.disabled = true;
const wrapper = mount(<SensorList {...p} />);
const toggle = wrapper.find("button");
toggle.first().simulate("click");
toggle.last().simulate("click");
expect(mockDevice.send).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,113 @@
import * as React from "react";
import { t } from "i18next";
import { error } from "farmbot-toastr";
import { SensorList } from "./sensor_list";
import { SensorForm } from "./sensor_form";
import { Widget, WidgetBody, WidgetHeader, SaveBtn } from "../../ui/index";
import { SensorsProps } from "../../devices/interfaces";
import { SensorState } from "./interfaces";
import {
TaggedSensor, getArrayStatus, SpecialStatus
} from "../../resources/tagged_resources";
import { saveAll, init } from "../../api/crud";
import { ToolTips } from "../../constants";
import { uniq } from "lodash";
export class Sensors extends React.Component<SensorsProps, SensorState> {
constructor(props: SensorsProps) {
super(props);
this.state = { isEditing: false };
}
toggle = () => {
this.setState({ isEditing: !this.state.isEditing });
}
maybeSave = () => {
const pinNums = this.props.sensors.map(x => x.body.pin);
const allAreUniq = uniq(pinNums).length === pinNums.length;
if (allAreUniq) {
this.props.dispatch(saveAll(this.props.sensors, this.toggle));
} else {
error(t("Pin numbers must be unique."));
}
}
showPins = () => {
const { sensors, dispatch, bot, disabled } = this.props;
const pins = bot.hardware.pins;
if (this.state.isEditing) {
return <SensorForm sensors={sensors}
dispatch={dispatch} />;
} else {
return <SensorList sensors={sensors}
dispatch={dispatch}
pins={pins}
disabled={disabled} />;
}
}
taggedSensor = (pin: number, label: string, mode: 0 | 1): TaggedSensor => {
return {
uuid: "WILL_BE_CHANGED_BY_REDUCER",
specialStatus: SpecialStatus.SAVED,
kind: "Sensor",
body: { pin, label, mode }
};
}
emptySensor = (): TaggedSensor => {
return this.taggedSensor(0, t("New Sensor"), 0);
}
stockSensors = (dispatch: Function) => {
const newSensor = (pin: number, label: string, mode: 0 | 1) => {
dispatch(init(this.taggedSensor(pin, label, mode)));
};
newSensor(63, t("Tool Verification"), 0);
newSensor(59, t("Soil Moisture"), 1);
}
render() {
const { dispatch, sensors } = this.props;
const { isEditing } = this.state;
const status = getArrayStatus(sensors);
return <Widget className="sensors-widget">
<WidgetHeader title={t("Sensors")} helpText={ToolTips.SENSORS}>
<button
className="fb-button gray"
onClick={this.toggle}
hidden={!!status}>
{!isEditing && t("Edit")}
{isEditing && t("Back")}
</button>
<SaveBtn
hidden={!isEditing}
status={status}
onClick={this.maybeSave} />
<button
hidden={!isEditing}
className="fb-button green"
type="button"
onClick={() => { dispatch(init(this.emptySensor())); }}>
<i className="fa fa-plus" />
</button>
<button
hidden={!isEditing}
className="fb-button green"
type="button"
onClick={() => this.stockSensors(dispatch)}>
<i className="fa fa-plus" />
{t("Stock sensors")}
</button>
</WidgetHeader>
<WidgetBody>
{this.showPins()}
</WidgetBody>
</Widget>;
}
}

View File

@ -0,0 +1,25 @@
import { TaggedSensor } from "../../resources/tagged_resources";
import { Pins } from "farmbot/dist";
export interface SensorState {
isEditing: boolean;
}
export interface Sensor {
id?: number;
pin: number | undefined;
mode: number;
label: string;
}
export interface SensorFormProps {
dispatch: Function;
sensors: TaggedSensor[];
}
export interface SensorListProps {
dispatch: Function;
sensors: TaggedSensor[];
pins: Pins;
disabled: boolean | undefined;
}

View File

@ -0,0 +1,56 @@
import * as React from "react";
import { t } from "i18next";
import { destroy, edit } from "../../api/crud";
import { SensorFormProps } from "./interfaces";
import { sortResourcesById } from "../../util";
import { Row, Col, FBSelect } from "../../ui";
import {
pinDropdowns
} from "../../sequences/step_tiles/pin_and_peripheral_support";
import { PIN_MODES } from "../../sequences/step_tiles/tile_pin_support";
export function SensorForm(props: SensorFormProps) {
const { dispatch, sensors } = props;
const modes: { [s: string]: string } = {
0: t("Digital"),
1: t("Analog")
};
return <div>
{sortResourcesById(sensors).map(p => {
return <Row key={p.uuid + p.body.id}>
<Col xs={4}>
<input type="text"
placeholder={t("Name")}
value={p.body.label}
onChange={e => dispatch(edit(p, {
label: e.currentTarget.value
}))} />
</Col>
<Col xs={3}>
<FBSelect
selectedItem={
{ label: `Pin ${p.body.pin}`, value: p.body.pin || "" }}
onChange={d => dispatch(edit(p, {
pin: parseInt(d.value.toString(), 10)
}))}
list={pinDropdowns(n => n)} />
</Col>
<Col xs={3}>
<FBSelect
onChange={d => {
dispatch(edit(p, { mode: parseInt(d.value.toString(), 10) }));
}}
selectedItem={{ label: modes[p.body.mode], value: p.body.mode }}
list={PIN_MODES} />
</Col>
<Col xs={2}>
<button
className="red fb-button"
onClick={() => dispatch(destroy(p.uuid))}>
<i className="fa fa-minus" />
</button>
</Col>
</Row>;
})}
</div>;
}

View File

@ -0,0 +1,64 @@
import * as React from "react";
import { readPin } from "../../devices/actions";
import { SensorListProps } from "./interfaces";
import { sortResourcesById } from "../../util";
import { Row, Col } from "../../ui";
import { t } from "i18next";
import { isNumber } from "lodash";
const SensorReadingDisplay =
({ label, value, mode }:
{ label: string, value: number | undefined, mode: number }) => {
const moistureSensor = label.toLowerCase().includes("moisture") ?
"moisture-sensor" : "";
return <div className={`sensor-reading-display ${moistureSensor}`}>
{isNumber(value) && value >= 0 &&
<div className="indicator"
style={{
left: `calc(${
(mode
? value / 1024 * 0.95 // analog
: value / 2) // digital
* 100}%)`,
width: `${mode ? 5 : 50}%`
}}>
<span style={{
marginLeft: `${mode
? `${value > 500 ? -3.5 : 1.5}rem`
: "7rem"}`,
color: `${mode ? "" : "white"}`
}}>
{value}
</span>
</div>}
</div>;
};
export function SensorList(props: SensorListProps) {
const { pins, disabled } = props;
return <div>
{sortResourcesById(props.sensors).map(p => {
const { label, mode, pin } = p.body;
const value = (pins[pin || -1] || { value: undefined }).value;
return <Row key={p.uuid + p.body.id}>
<Col xs={3}>
<label>{label}</label>
</Col>
<Col xs={1}>
<p>{pin}</p>
</Col>
<Col xs={6}>
<SensorReadingDisplay label={label} value={value} mode={mode} />
</Col>
<Col xs={2}>
<button
className={"fb-button gray"}
disabled={disabled}
onClick={() => readPin(pin || 0, `pin${pin}`, mode)}>
{t("read sensor")}
</button>
</Col>
</Row>;
})}
</div>;
}

View File

@ -2,20 +2,27 @@ import { Everything } from "../interfaces";
import {
selectAllPeripherals,
selectAllWebcamFeeds,
getFirmwareConfig
getFirmwareConfig,
selectAllSensors,
maybeGetDevice
} from "../resources/selectors";
import { Props } from "./interfaces";
import { maybeFetchUser } from "../resources/selectors";
import * as _ from "lodash";
import { validFwConfig } from "../util";
import {
validFwConfig, shouldDisplay, determineInstalledOsVersion
} from "../util";
export function mapStateToProps(props: Everything): Props {
const peripherals = _.uniq(selectAllPeripherals(props.resources.index));
const sensors = _.uniq(selectAllSensors(props.resources.index));
const resources = props.resources;
const bot2mqtt = props.bot.connectivity["bot.mqtt"];
const botToMqttStatus = bot2mqtt ? bot2mqtt.state : "down";
const fwConfig = validFwConfig(getFirmwareConfig(props.resources.index));
const { mcu_params } = props.bot.hardware;
const installedOsVersion = determineInstalledOsVersion(
props.bot, maybeGetDevice(props.resources.index));
return {
feeds: selectAllWebcamFeeds(resources.index),
@ -23,7 +30,9 @@ export function mapStateToProps(props: Everything): Props {
bot: props.bot,
user: maybeFetchUser(props.resources.index),
peripherals,
sensors,
botToMqttStatus,
firmwareSettings: fwConfig || mcu_params,
shouldDisplay: shouldDisplay(installedOsVersion, props.bot.minOsFeatureData),
};
}

View File

@ -659,3 +659,32 @@ ul {
margin-top: 1rem;
}
}
.sensors-widget {
p {
margin-top: 0.75rem;
}
.fa-plus {
margin-right: 0.5rem;
}
.sensor-reading-display {
&.moisture-sensor {
background: linear-gradient(to right, #a4c2f400 20%, #a4c2f4ff 80%, #a4c2f400 85%);
}
border-style: solid;
border-color: $dark_gray;
border-width: 0.1px;
height: 2rem;
width: 100%;
margin-top: 0.5rem;
.indicator {
background: $dark_gray;
height: 2rem;
position: relative;
span {
position: relative;
top: 0.15rem;
}
}
}
}

View File

@ -6,7 +6,7 @@ import { getDevice } from "../device";
import { Log, Everything } from "../interfaces";
import { GithubRelease, MoveRelProps, MinOsFeatureLookup, SourceFwConfig } from "./interfaces";
import { Thunk, GetState, ReduxAction } from "../redux/interfaces";
import { McuParams, Configuration } from "farmbot";
import { McuParams, Configuration, rpcRequest } from "farmbot";
import { Sequence } from "../sequences/interfaces";
import { ControlPanelState } from "../devices/interfaces";
import { API } from "../api/index";
@ -281,6 +281,15 @@ export function pinToggle(pin_number: number) {
.then(_.noop, commandErr(noun));
}
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 }
}]))
.then(_.noop, commandErr(noun));
}
export function homeAll(speed: number) {
const noun = "'Home All' command";
getDevice()

View File

@ -10,7 +10,8 @@ import { AuthState } from "../auth/interfaces";
import {
TaggedImage,
TaggedPeripheral,
TaggedDevice
TaggedDevice,
TaggedSensor
} from "../resources/tagged_resources";
import { ResourceIndex } from "../resources/interfaces";
import { TaggedUser } from "../resources/tagged_resources";
@ -54,6 +55,7 @@ export type ShouldDisplay = (x: Feature) => boolean;
/** Names of features that use minimum FBOS version checking. */
export enum Feature {
named_pins = "named_pins",
sensors = "sensors",
change_ownership = "change_ownership",
variables = "variables",
jest_feature = "jest_feature", // for tests
@ -180,6 +182,13 @@ export interface PeripheralsProps {
disabled: boolean | undefined;
}
export interface SensorsProps {
bot: BotState;
sensors: TaggedSensor[];
dispatch: Function;
disabled: boolean | undefined;
}
export interface FarmwareProps {
dispatch: Function;
env: Partial<WD_ENV>;