api pin bindings
parent
f7a40217c5
commit
d4ffd61409
|
@ -6,7 +6,8 @@ import {
|
|||
TaggedPlantPointer, TaggedGenericPointer, TaggedPeripheral, TaggedFbosConfig,
|
||||
TaggedWebAppConfig,
|
||||
TaggedSensor,
|
||||
TaggedFirmwareConfig
|
||||
TaggedFirmwareConfig,
|
||||
TaggedPinBinding
|
||||
} from "../../resources/tagged_resources";
|
||||
import { ExecutableType } from "../../farm_designer/interfaces";
|
||||
import { fakeResource } from "../fake_resource";
|
||||
|
@ -123,6 +124,14 @@ export function fakeWebcamFeed(): TaggedWebcamFeed {
|
|||
});
|
||||
}
|
||||
|
||||
export function fakePinBinding(): TaggedPinBinding {
|
||||
return fakeResource("PinBinding", {
|
||||
id: idCounter++,
|
||||
pin_num: 10,
|
||||
sequence_id: 1
|
||||
});
|
||||
}
|
||||
|
||||
export function fakeSensor(): TaggedSensor {
|
||||
return fakeResource("Sensor", {
|
||||
id: idCounter++,
|
||||
|
@ -131,6 +140,7 @@ export function fakeSensor(): TaggedSensor {
|
|||
pin: 1
|
||||
});
|
||||
}
|
||||
|
||||
export function fakePeripheral(): TaggedPeripheral {
|
||||
return fakeResource("Peripheral", {
|
||||
id: ++idCounter,
|
||||
|
|
|
@ -129,7 +129,7 @@ export class API {
|
|||
/** /api/device_configs/:id */
|
||||
get deviceConfigPath() { return `${this.baseUrl}/api/device_configs`; }
|
||||
/** /api/pin_bindings/:id */
|
||||
get pinBindingPath() { return `${this.baseUrl}/api/pin_bindings`; }
|
||||
get pinBindingPath() { return `${this.baseUrl}/api/pin_bindings/`; }
|
||||
/** /api/farmware_installations/:id */
|
||||
get farmwareInstallationPath() {
|
||||
return `${this.baseUrl}/api/farmware_installations`;
|
||||
|
|
|
@ -213,6 +213,7 @@ export function urlFor(tag: ResourceName) {
|
|||
Regimen: API.current.regimensPath,
|
||||
Peripheral: API.current.peripheralsPath,
|
||||
Sensor: API.current.sensorPath,
|
||||
PinBinding: API.current.pinBindingPath,
|
||||
Point: API.current.pointsPath,
|
||||
User: API.current.usersPath,
|
||||
Device: API.current.devicePath,
|
||||
|
|
|
@ -6,6 +6,11 @@ jest.mock("../../../device", () => ({
|
|||
getDevice: () => (mockDevice)
|
||||
}));
|
||||
|
||||
jest.mock("../../../api/crud", () => ({
|
||||
destroy: jest.fn(),
|
||||
initSave: jest.fn()
|
||||
}));
|
||||
|
||||
import * as React from "react";
|
||||
import { PinBindings, PinBindingsProps } from "../pin_bindings";
|
||||
import { mount } from "enzyme";
|
||||
|
@ -14,7 +19,10 @@ import {
|
|||
buildResourceIndex
|
||||
} from "../../../__test_support__/resource_index_builder";
|
||||
import { TaggedSequence } from "../../../resources/tagged_resources";
|
||||
import { fakeSequence } from "../../../__test_support__/fake_state/resources";
|
||||
import {
|
||||
fakeSequence, fakePinBinding
|
||||
} from "../../../__test_support__/fake_state/resources";
|
||||
import { destroy, initSave } from "../../../api/crud";
|
||||
|
||||
describe("<PinBindings/>", () => {
|
||||
beforeEach(function () {
|
||||
|
@ -37,12 +45,13 @@ describe("<PinBindings/>", () => {
|
|||
dispatch: jest.fn(),
|
||||
bot: bot,
|
||||
resources: resources,
|
||||
botToMqttStatus: "up"
|
||||
botToMqttStatus: "up",
|
||||
shouldDisplay: x => false,
|
||||
};
|
||||
}
|
||||
|
||||
it("renders", () => {
|
||||
const wrapper = mount(<PinBindings {...fakeProps() } />);
|
||||
const wrapper = mount(<PinBindings {...fakeProps()} />);
|
||||
["pin bindings", "pin number", "none", "bind"].map(string =>
|
||||
expect(wrapper.text().toLowerCase()).toContain(string));
|
||||
["pi gpio 10", "sequence 1", "pi gpio 11", "sequence 2"].map(string =>
|
||||
|
@ -52,39 +61,63 @@ describe("<PinBindings/>", () => {
|
|||
expect(buttons.length).toBe(4);
|
||||
});
|
||||
|
||||
it("unregisters pin", () => {
|
||||
const dispatch = jest.fn();
|
||||
it("unregisters pin: bot", () => {
|
||||
const p = fakeProps();
|
||||
p.dispatch = dispatch;
|
||||
p.dispatch = jest.fn(x => x(jest.fn()));
|
||||
const wrapper = mount(<PinBindings {...p} />);
|
||||
const buttons = wrapper.find("button");
|
||||
buttons.first().simulate("click");
|
||||
dispatch.mock.calls[0][0](jest.fn());
|
||||
expect(mockDevice.unregisterGpio).toHaveBeenCalledWith({
|
||||
pin_number: 10
|
||||
});
|
||||
});
|
||||
|
||||
it("registers pin", () => {
|
||||
const dispatch = jest.fn();
|
||||
it("unregisters pin: api", () => {
|
||||
const p = fakeProps();
|
||||
p.dispatch = dispatch;
|
||||
const s = fakeSequence();
|
||||
s.body.id = 1;
|
||||
p.resources = buildResourceIndex([fakePinBinding(), s]).index;
|
||||
p.shouldDisplay = x => true;
|
||||
const wrapper = mount(<PinBindings {...p} />);
|
||||
const buttons = wrapper.find("button");
|
||||
buttons.first().simulate("click");
|
||||
expect(mockDevice.unregisterGpio).not.toHaveBeenCalled();
|
||||
expect(destroy).toHaveBeenCalledWith(expect.stringContaining("PinBinding"));
|
||||
});
|
||||
|
||||
it("registers pin: bot", () => {
|
||||
const p = fakeProps();
|
||||
p.dispatch = jest.fn(x => x(jest.fn()));
|
||||
const wrapper = mount(<PinBindings {...p} />);
|
||||
const buttons = wrapper.find("button");
|
||||
expect(buttons.last().text()).toEqual("BIND");
|
||||
wrapper.setState({ pinNumberInput: 1, sequenceIdInput: 2 });
|
||||
buttons.last().simulate("click");
|
||||
dispatch.mock.calls[0][0](jest.fn());
|
||||
expect(mockDevice.registerGpio).toHaveBeenCalledWith({
|
||||
pin_number: 1, sequence_id: 2
|
||||
});
|
||||
});
|
||||
|
||||
it("registers pin: api", () => {
|
||||
const p = fakeProps();
|
||||
p.dispatch = jest.fn();
|
||||
p.shouldDisplay = x => true;
|
||||
const wrapper = mount(<PinBindings {...p} />);
|
||||
const buttons = wrapper.find("button");
|
||||
expect(buttons.last().text()).toEqual("BIND");
|
||||
wrapper.setState({ pinNumberInput: 1, sequenceIdInput: 2 });
|
||||
buttons.last().simulate("click");
|
||||
expect(mockDevice.registerGpio).not.toHaveBeenCalled();
|
||||
expect(initSave).toHaveBeenCalledWith(expect.objectContaining({
|
||||
body: { pin_num: 1, sequence_id: 2 }, kind: "PinBinding"
|
||||
}));
|
||||
});
|
||||
|
||||
it("sets sequence id", () => {
|
||||
const p = fakeProps();
|
||||
const s = p.resources.references[p.resources.byKind.Sequence[0]];
|
||||
const id = s && s.body.id;
|
||||
const wrapper = mount(<PinBindings {...p } />);
|
||||
const wrapper = mount(<PinBindings {...p} />);
|
||||
expect(wrapper.state().sequenceIdInput).toEqual(undefined);
|
||||
// tslint:disable-next-line:no-any
|
||||
const instance = wrapper.instance() as any;
|
||||
|
@ -93,7 +126,7 @@ describe("<PinBindings/>", () => {
|
|||
});
|
||||
|
||||
it("sets pin", () => {
|
||||
const wrapper = mount(<PinBindings {...fakeProps() } />);
|
||||
const wrapper = mount(<PinBindings {...fakeProps()} />);
|
||||
expect(wrapper.state().pinNumberInput).toEqual(undefined);
|
||||
// tslint:disable-next-line:no-any
|
||||
const instance = wrapper.instance() as any;
|
||||
|
|
|
@ -8,9 +8,9 @@ import {
|
|||
DropDownItem
|
||||
} from "../../ui/index";
|
||||
import { ToolTips } from "../../constants";
|
||||
import { BotState } from "../interfaces";
|
||||
import { BotState, ShouldDisplay, Feature } from "../interfaces";
|
||||
import { registerGpioPin, unregisterGpioPin } from "../actions";
|
||||
import { findSequenceById } from "../../resources/selectors";
|
||||
import { findSequenceById, selectAllPinBindings } from "../../resources/selectors";
|
||||
import { ResourceIndex } from "../../resources/interfaces";
|
||||
import { MustBeOnline } from "../must_be_online";
|
||||
import { Popover, Position } from "@blueprintjs/core";
|
||||
|
@ -18,12 +18,15 @@ import { RpiGpioDiagram, gpio } from "./rpi_gpio_diagram";
|
|||
import { error } from "farmbot-toastr";
|
||||
import { NetworkState } from "../../connectivity/interfaces";
|
||||
import { SequenceSelectBox } from "../../sequences/sequence_select_box";
|
||||
import { initSave, destroy } from "../../api/crud";
|
||||
import { TaggedPinBinding, SpecialStatus } from "../../resources/tagged_resources";
|
||||
|
||||
export interface PinBindingsProps {
|
||||
bot: BotState;
|
||||
dispatch: Function;
|
||||
botToMqttStatus: NetworkState;
|
||||
resources: ResourceIndex;
|
||||
shouldDisplay: ShouldDisplay;
|
||||
}
|
||||
|
||||
export interface PinBindingsState {
|
||||
|
@ -49,6 +52,30 @@ export class PinBindings
|
|||
};
|
||||
}
|
||||
|
||||
get pinBindings(): {
|
||||
pin_number: number, sequence_id: number, uuid?: string
|
||||
}[] {
|
||||
if (this.props.shouldDisplay(Feature.api_pin_bindings)) {
|
||||
return selectAllPinBindings(this.props.resources)
|
||||
.map(x => {
|
||||
return {
|
||||
pin_number: x.body.pin_num,
|
||||
sequence_id: x.body.sequence_id,
|
||||
uuid: x.uuid
|
||||
};
|
||||
});
|
||||
} else {
|
||||
const { gpio_registry } = this.props.bot.hardware;
|
||||
return Object.entries(gpio_registry || {})
|
||||
.map(([pin_number, sequence_id]) => {
|
||||
return {
|
||||
pin_number: parseInt(pin_number),
|
||||
sequence_id: parseInt(sequence_id || "")
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
changeSelection = (input: DropDownItem) => {
|
||||
this.setState({ sequenceIdInput: parseInt(input.value as string) });
|
||||
}
|
||||
|
@ -65,13 +92,28 @@ export class PinBindings
|
|||
}
|
||||
}
|
||||
|
||||
taggedPinBinding =
|
||||
(pin_num: number, sequence_id: number): TaggedPinBinding => {
|
||||
return {
|
||||
uuid: "WILL_BE_CHANGED_BY_REDUCER",
|
||||
specialStatus: SpecialStatus.SAVED,
|
||||
kind: "PinBinding",
|
||||
body: { pin_num, sequence_id }
|
||||
};
|
||||
}
|
||||
|
||||
bindPin = () => {
|
||||
const { pinNumberInput, sequenceIdInput } = this.state;
|
||||
if (pinNumberInput && sequenceIdInput) {
|
||||
this.props.dispatch(registerGpioPin({
|
||||
pin_number: pinNumberInput,
|
||||
sequence_id: sequenceIdInput
|
||||
}));
|
||||
if (this.props.shouldDisplay(Feature.api_pin_bindings)) {
|
||||
this.props.dispatch(initSave(
|
||||
this.taggedPinBinding(pinNumberInput, sequenceIdInput)));
|
||||
} else {
|
||||
this.props.dispatch(registerGpioPin({
|
||||
pin_number: pinNumberInput,
|
||||
sequence_id: sequenceIdInput
|
||||
}));
|
||||
}
|
||||
this.setState({
|
||||
pinNumberInput: undefined,
|
||||
sequenceIdInput: undefined
|
||||
|
@ -79,37 +121,41 @@ export class PinBindings
|
|||
}
|
||||
}
|
||||
|
||||
deleteBinding = (pin: number, uuid?: string) => {
|
||||
if (this.props.shouldDisplay(Feature.api_pin_bindings)) {
|
||||
this.props.dispatch(destroy(uuid || ""));
|
||||
} else {
|
||||
this.props.dispatch(unregisterGpioPin(pin));
|
||||
}
|
||||
}
|
||||
|
||||
get boundPins(): number[] | undefined {
|
||||
const { gpio_registry } = this.props.bot.hardware;
|
||||
return gpio_registry && Object.keys(gpio_registry).map(x => parseInt(x));
|
||||
return this.pinBindings.map(x => x.pin_number);
|
||||
}
|
||||
|
||||
currentBindingsList = () => {
|
||||
const { bot, dispatch, resources } = this.props;
|
||||
const { gpio_registry } = bot.hardware;
|
||||
const { resources } = this.props;
|
||||
return <div className={"bindings-list"}>
|
||||
{gpio_registry &&
|
||||
Object.entries(gpio_registry)
|
||||
.map(([pin_number, sequence_id]) => {
|
||||
return <Row key={`pin_${pin_number}_binding`}>
|
||||
<Col xs={ColumnWidth.pin}>
|
||||
{`Pi GPIO ${pin_number}`}
|
||||
</Col>
|
||||
<Col xs={ColumnWidth.sequence}>
|
||||
{sequence_id ? findSequenceById(
|
||||
resources, parseInt(sequence_id)).body.name : ""}
|
||||
</Col>
|
||||
<Col xs={ColumnWidth.button}>
|
||||
<button
|
||||
className="fb-button red"
|
||||
onClick={() => {
|
||||
dispatch(unregisterGpioPin(parseInt(pin_number)));
|
||||
}}>
|
||||
<i className="fa fa-minus" />
|
||||
</button>
|
||||
</Col>
|
||||
</Row>;
|
||||
})}
|
||||
{this.pinBindings
|
||||
.map(x => {
|
||||
const { pin_number, sequence_id } = x;
|
||||
return <Row key={`pin_${pin_number}_binding`}>
|
||||
<Col xs={ColumnWidth.pin}>
|
||||
{`Pi GPIO ${pin_number}`}
|
||||
</Col>
|
||||
<Col xs={ColumnWidth.sequence}>
|
||||
{sequence_id ? findSequenceById(
|
||||
resources, sequence_id).body.name : ""}
|
||||
</Col>
|
||||
<Col xs={ColumnWidth.button}>
|
||||
<button
|
||||
className="fb-button red"
|
||||
onClick={() => this.deleteBinding(pin_number, x.uuid)}>
|
||||
<i className="fa fa-minus" />
|
||||
</button>
|
||||
</Col>
|
||||
</Row>;
|
||||
})}
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
|
|
@ -94,7 +94,8 @@ export class Devices extends React.Component<Props, {}> {
|
|||
dispatch={this.props.dispatch}
|
||||
bot={this.props.bot}
|
||||
resources={this.props.resources}
|
||||
botToMqttStatus={botToMqttStatus} />}
|
||||
botToMqttStatus={botToMqttStatus}
|
||||
shouldDisplay={this.props.shouldDisplay} />}
|
||||
</Col>
|
||||
</Row>
|
||||
</Page>;
|
||||
|
|
|
@ -59,6 +59,7 @@ export enum Feature {
|
|||
sensors = "sensors",
|
||||
change_ownership = "change_ownership",
|
||||
variables = "variables",
|
||||
api_pin_bindings = "api_pin_bindings",
|
||||
jest_feature = "jest_feature", // for tests
|
||||
}
|
||||
/** Object fetched from FEATURE_MIN_VERSIONS_URL. */
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
TaggedPeripheral,
|
||||
TaggedWebAppConfig,
|
||||
TaggedFirmwareConfig,
|
||||
TaggedPinBinding,
|
||||
} from "./tagged_resources";
|
||||
import { sortResourcesById } from "../util";
|
||||
import { error } from "farmbot-toastr";
|
||||
|
@ -69,6 +70,8 @@ export const selectAllPeripherals =
|
|||
export const selectAllPoints = (i: ResourceIndex) => findAll<TaggedPoint>(i, "Point");
|
||||
export const selectAllRegimens = (i: ResourceIndex) => findAll<TaggedRegimen>(i, "Regimen");
|
||||
export const selectAllSensors = (i: ResourceIndex) => findAll<TaggedSensor>(i, "Sensor");
|
||||
export const selectAllPinBindings =
|
||||
(i: ResourceIndex) => findAll<TaggedPinBinding>(i, "PinBinding");
|
||||
export const selectAllSequences = (i: ResourceIndex) => findAll<TaggedSequence>(i, "Sequence");
|
||||
export const selectAllTools = (i: ResourceIndex) => findAll<TaggedTool>(i, "Tool");
|
||||
export const selectAllSavedSensors =
|
||||
|
|
Loading…
Reference in New Issue