220 lines
6.8 KiB
TypeScript
220 lines
6.8 KiB
TypeScript
import {
|
|
selectAllSavedPeripherals,
|
|
selectAllSavedSensors
|
|
} from "../../resources/selectors";
|
|
import { ResourceIndex } from "../../resources/interfaces";
|
|
import { DropDownItem } from "../../ui";
|
|
import { range, isNumber, isString } from "lodash";
|
|
import {
|
|
TaggedPeripheral, TaggedSensor, ResourceName, Nothing
|
|
} from "farmbot";
|
|
import { ReadPin, AllowedPinTypes, NamedPin } from "farmbot";
|
|
import { bail } from "../../util/errors";
|
|
import { StepParams } from "../interfaces";
|
|
import { editStep } from "../../api/crud";
|
|
import { ShouldDisplay, Feature } from "../../devices/interfaces";
|
|
import { joinKindAndId } from "../../resources/reducer_support";
|
|
import { t } from "../../i18next_wrapper";
|
|
|
|
/** `headingIds` required to group the four kinds of pins. */
|
|
export enum PinGroupName {
|
|
Sensor = "Sensor",
|
|
Peripheral = "Peripheral",
|
|
BoxLed = "Box LED",
|
|
Pin = "Pin",
|
|
Position = "Position"
|
|
}
|
|
|
|
/** User-controllable box LEDs connected to the RPi. */
|
|
export enum BoxLed {
|
|
BoxLed3 = "BoxLed3",
|
|
BoxLed4 = "BoxLed4",
|
|
}
|
|
|
|
const BOX_LED_LABELS: { [x: string]: string } = {
|
|
[BoxLed.BoxLed3]: t("Box LED 3"),
|
|
[BoxLed.BoxLed4]: t("Box LED 4"),
|
|
};
|
|
|
|
export const PERIPHERAL_HEADING: DropDownItem =
|
|
({ heading: true, label: t("Peripherals"), value: 0 });
|
|
|
|
export const SENSOR_HEADING: DropDownItem =
|
|
({ heading: true, label: t("Sensors"), value: 0 });
|
|
|
|
export const BOX_LED_HEADING: DropDownItem =
|
|
({ heading: true, label: t("Box LEDs"), value: 0 });
|
|
|
|
export const PIN_HEADING: DropDownItem =
|
|
({ heading: true, label: t("Pins"), value: 0 });
|
|
|
|
/** Pass it the number X and it will generate a DropDownItem for `pin x`. */
|
|
export const pinNumber2DropDown =
|
|
(valueFormat: (n: number) => (string | number)) =>
|
|
(n: number): DropDownItem => {
|
|
const analog = n > 53 ? ` (A${n - 54})` : "";
|
|
const label = `${t("Pin")} ${n}${analog}`;
|
|
return { label, value: valueFormat(n), headingId: PinGroupName.Pin };
|
|
};
|
|
|
|
const peripheral2DropDown =
|
|
(x: TaggedPeripheral): DropDownItem => ({
|
|
label: x.body.label,
|
|
value: x.uuid,
|
|
headingId: PinGroupName.Peripheral
|
|
});
|
|
|
|
const sensor2DropDown =
|
|
(x: TaggedSensor): DropDownItem => ({
|
|
label: x.body.label,
|
|
value: x.uuid,
|
|
headingId: PinGroupName.Sensor
|
|
});
|
|
|
|
const boxLed2DropDown =
|
|
(boxLed: BoxLed): DropDownItem => ({
|
|
label: t(BOX_LED_LABELS[boxLed]),
|
|
value: boxLed,
|
|
headingId: PinGroupName.BoxLed
|
|
});
|
|
|
|
export function peripheralsAsDropDowns(input: ResourceIndex): DropDownItem[] {
|
|
const list = selectAllSavedPeripherals(input).map(peripheral2DropDown);
|
|
return list.length ? [PERIPHERAL_HEADING, ...list] : [];
|
|
}
|
|
|
|
export function sensorsAsDropDowns(input: ResourceIndex): DropDownItem[] {
|
|
const list = selectAllSavedSensors(input).map(sensor2DropDown);
|
|
return list.length ? [SENSOR_HEADING, ...list] : [];
|
|
}
|
|
|
|
export function boxLedsAsDropDowns(): DropDownItem[] {
|
|
const list = Object.values(BoxLed).map(boxLed2DropDown);
|
|
return [BOX_LED_HEADING, ...list];
|
|
}
|
|
|
|
/** Number of pins in an Arduino Mega */
|
|
export const PIN_RANGE = range(0, 70);
|
|
|
|
export function pinDropdowns(
|
|
valueFormat: (n: number) => string | number): DropDownItem[] {
|
|
return [PIN_HEADING, ...PIN_RANGE.map(pinNumber2DropDown(valueFormat))];
|
|
}
|
|
|
|
export const pinsAsDropDownsWritePin = (
|
|
input: ResourceIndex, shouldDisplay: ShouldDisplay, showPins: boolean
|
|
): DropDownItem[] => [
|
|
...(shouldDisplay(Feature.named_pins) ? peripheralsAsDropDowns(input) : []),
|
|
...(shouldDisplay(Feature.rpi_led_control) ? boxLedsAsDropDowns() : []),
|
|
...(showPins ? pinDropdowns(n => n) : []),
|
|
];
|
|
|
|
export const pinsAsDropDownsReadPin = (
|
|
input: ResourceIndex, shouldDisplay: ShouldDisplay, showPins: boolean
|
|
): DropDownItem[] => [
|
|
...(shouldDisplay(Feature.named_pins) ? sensorsAsDropDowns(input) : []),
|
|
...(shouldDisplay(Feature.named_pins) ? peripheralsAsDropDowns(input) : []),
|
|
...(showPins ? pinDropdowns(n => n) : []),
|
|
];
|
|
|
|
const TYPE_MAPPING: Record<AllowedPinTypes, PinGroupName | BoxLed> = {
|
|
"Peripheral": PinGroupName.Peripheral,
|
|
"Sensor": PinGroupName.Sensor,
|
|
"BoxLed3": BoxLed.BoxLed3,
|
|
"BoxLed4": BoxLed.BoxLed4,
|
|
};
|
|
|
|
export const isPinType =
|
|
(x: string): x is AllowedPinTypes => !!TYPE_MAPPING[x as AllowedPinTypes];
|
|
|
|
const NO = "UUID or ID not found";
|
|
|
|
export const findByPinNumber =
|
|
(ri: ResourceIndex, input: NamedPin): TaggedPeripheral | TaggedSensor => {
|
|
const { pin_type, pin_id } = input.args;
|
|
const kindAndId = joinKindAndId(pin_type as ResourceName, pin_id);
|
|
const r = ri.references[ri.byKindAndId[kindAndId] || NO] || bail(NO);
|
|
switch (r.kind) {
|
|
case "Peripheral":
|
|
case "Sensor": return r;
|
|
default: return bail("Not a Peripheral or Sensor");
|
|
}
|
|
};
|
|
|
|
export function namedPin2DropDown(ri: ResourceIndex, input: NamedPin | Nothing):
|
|
DropDownItem {
|
|
if (input.kind === "named_pin") {
|
|
const { pin_type } = input.args;
|
|
if (isPinType(pin_type)) {
|
|
switch (pin_type) {
|
|
case BoxLed.BoxLed3:
|
|
case BoxLed.BoxLed4:
|
|
return boxLed2DropDown(pin_type as BoxLed);
|
|
case PinGroupName.Peripheral:
|
|
case PinGroupName.Sensor:
|
|
default:
|
|
const item = findByPinNumber(ri, input);
|
|
switch (item.kind) {
|
|
case PinGroupName.Peripheral:
|
|
return peripheral2DropDown(item);
|
|
case PinGroupName.Sensor:
|
|
return sensor2DropDown(item);
|
|
}
|
|
}
|
|
} else {
|
|
bail("Bad pin_type: " + JSON.stringify(pin_type));
|
|
}
|
|
}
|
|
return { label: t("Select a pin"), value: "", isNull: true };
|
|
}
|
|
|
|
export const dropDown2CeleryArg =
|
|
(ri: ResourceIndex, item: DropDownItem): number | NamedPin => {
|
|
if (isString(item.value)) { // str means "Named Pin". num means "Raw Pin"
|
|
if (Object.values(BoxLed).includes(item.value)) {
|
|
return {
|
|
kind: "named_pin",
|
|
args: { pin_type: item.value as AllowedPinTypes, pin_id: -1 }
|
|
};
|
|
}
|
|
const uuid: string = item.value;
|
|
const r = ri.references[uuid];
|
|
if (r) {
|
|
return {
|
|
kind: "named_pin",
|
|
args: { pin_type: r.kind as AllowedPinTypes, pin_id: r.body.id || -99 }
|
|
};
|
|
} else {
|
|
return bail("Bad uuid in celery arg: " + uuid);
|
|
}
|
|
} else {
|
|
return item.value;
|
|
}
|
|
};
|
|
|
|
export const setArgsDotPinNumber =
|
|
(x: StepParams) => (d: DropDownItem) => {
|
|
const { dispatch, currentSequence, index, resources, currentStep } = x;
|
|
|
|
dispatch(editStep({
|
|
step: currentStep,
|
|
sequence: currentSequence,
|
|
index: index,
|
|
executor(c) {
|
|
switch (c.kind) {
|
|
case "read_pin":
|
|
case "write_pin":
|
|
c.args.pin_number = dropDown2CeleryArg(resources, d);
|
|
}
|
|
}
|
|
}));
|
|
};
|
|
|
|
type PinNumber = ReadPin["args"]["pin_number"];
|
|
|
|
export function celery2DropDown(input: PinNumber, ri: ResourceIndex): DropDownItem {
|
|
return isNumber(input)
|
|
? pinNumber2DropDown(n => n)(input)
|
|
: namedPin2DropDown(ri, input);
|
|
}
|