sensors UI
parent
32f8552c98
commit
ac698edb73
|
@ -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";
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.`);
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -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));
|
||||
});
|
||||
});
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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>;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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>;
|
||||
}
|
|
@ -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>;
|
||||
}
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>;
|
||||
|
|
Loading…
Reference in New Issue