,
@@ -397,6 +397,7 @@ const ReadingsListItem = (props: ReadingsListItemProps) =>
const sensorName = `${props.sensorNameByPinLookup[pin]} (pin ${pin})`;
return
", () => {
cropPhotos: false,
showUncroppedArea: false,
soilHeightLabels: false,
- getSoilHeightColor: () => "rgb(128, 128, 128)",
+ getSoilHeightColor: () => ({ rgb: "rgb(128, 128, 128)", a: 1 }),
current: false,
animate: false,
});
@@ -125,7 +125,7 @@ describe("
", () => {
const wrapper = svgMount(
);
expect(wrapper.text()).toContain("-100");
expect(wrapper.find("text").first().props().fill)
- .toEqual(p.getSoilHeightColor(-100));
+ .toEqual(p.getSoilHeightColor(-100).rgb);
expect(wrapper.find("text").first().props().stroke).toEqual(Color.black);
});
diff --git a/frontend/farm_designer/map/layers/points/garden_point.tsx b/frontend/farm_designer/map/layers/points/garden_point.tsx
index 1e5a28b4fc..f56c16c972 100644
--- a/frontend/farm_designer/map/layers/points/garden_point.tsx
+++ b/frontend/farm_designer/map/layers/points/garden_point.tsx
@@ -46,7 +46,7 @@ export const GardenPoint = (props: GardenPointProps) => {
{props.soilHeightLabels && soilHeightPoint(point) &&
{ rgb: string, a: number };
+
export enum InterpolationKey {
data = "interpolationData",
hash = "interpolationHash",
@@ -80,8 +82,8 @@ export const getZAtLocation =
interface GenerateInterpolationMapDataProps {
kind: "Point" | "SensorReading";
points: (TaggedGenericPointer | TaggedSensorReading)[];
- mapTransformProps: MapTransformProps;
- getColor(z: number): string;
+ gridSize: AxisNumberProperty;
+ getColor: GetColor;
options: InterpolationOptions;
}
@@ -104,10 +106,10 @@ const convertToPointObject =
export const generateData = (props: GenerateInterpolationMapDataProps) => {
const points = selectMostRecentPoints(props.points);
- const { gridSize } = props.mapTransformProps;
+ const { gridSize } = props;
const { stepSize } = props.options;
const hash = [
- JSON.stringify(points),
+ JSON.stringify(points.map(p => p.uuid)),
JSON.stringify(gridSize),
JSON.stringify(props.options),
].join("");
@@ -160,7 +162,7 @@ interface InterpolationMapProps {
kind: "Point" | "SensorReading";
points: (TaggedGenericPointer | TaggedSensorReading)[];
mapTransformProps: MapTransformProps;
- getColor(z: number): string;
+ getColor: GetColor;
options: InterpolationOptions;
}
@@ -174,11 +176,12 @@ export const InterpolationMap = (props: InterpolationMapProps) => {
const { quadrant } = props.mapTransformProps;
const xOffset = [1, 4].includes(quadrant);
const yOffset = [3, 4].includes(quadrant);
+ const colorInfo = props.getColor(z);
return ;
+ fill={colorInfo.rgb} fillOpacity={colorInfo.a} />;
})}
;
diff --git a/frontend/farm_designer/map/layers/points/point_layer.tsx b/frontend/farm_designer/map/layers/points/point_layer.tsx
index ce6cbffb30..64dca51876 100644
--- a/frontend/farm_designer/map/layers/points/point_layer.tsx
+++ b/frontend/farm_designer/map/layers/points/point_layer.tsx
@@ -36,7 +36,11 @@ export function PointLayer(props: PointLayerProps) {
props.interactions ? {} : { pointerEvents: "none" };
const options = fetchInterpolationOptions(props.farmwareEnvs);
generateData({
- kind: "Point", points: soilHeightPoints, mapTransformProps, getColor, options,
+ kind: "Point",
+ points: soilHeightPoints,
+ gridSize: mapTransformProps.gridSize,
+ getColor,
+ options,
});
return
{props.overlayVisible &&
diff --git a/frontend/farm_designer/map/layers/sensor_readings/__tests__/sensor_readings_layer_test.tsx b/frontend/farm_designer/map/layers/sensor_readings/__tests__/sensor_readings_layer_test.tsx
index 203430cdea..c929a8a8e0 100644
--- a/frontend/farm_designer/map/layers/sensor_readings/__tests__/sensor_readings_layer_test.tsx
+++ b/frontend/farm_designer/map/layers/sensor_readings/__tests__/sensor_readings_layer_test.tsx
@@ -1,5 +1,6 @@
import React from "react";
import {
+ getMoistureColor,
SensorReadingsLayer, SensorReadingsLayerProps,
} from "../sensor_readings_layer";
import {
@@ -48,7 +49,7 @@ describe("", () => {
p.sensorReadings[0].body.mode = ANALOG;
const reading = fakeSensorReading();
reading.body.mode = ANALOG;
- reading.body.value = 1000;
+ reading.body.value = 800;
reading.body.x = 100;
reading.body.y = 200;
p.sensorReadings.push(reading);
@@ -58,3 +59,17 @@ describe("", () => {
expect(layer.find("rect").length).toEqual(1800);
});
});
+
+describe("getMoistureColor()", () => {
+ it.each<[number, string, number]>([
+ [0, "rgb(0, 0, 255)", 0],
+ [200, "rgb(0, 0, 255)", 0],
+ [700, "rgb(0, 0, 255)", 0.2],
+ [900, "rgb(0, 0, 255)", 0.42],
+ [1024, "rgb(0, 0, 0)", 0],
+ ])("returns color for %s: %s %s", (value, color, alpha) => {
+ const c = getMoistureColor(value);
+ expect(c.rgb).toEqual(color);
+ expect(c.a).toEqual(alpha);
+ });
+});
diff --git a/frontend/farm_designer/map/layers/sensor_readings/sensor_readings_layer.tsx b/frontend/farm_designer/map/layers/sensor_readings/sensor_readings_layer.tsx
index f8d3d11f85..020b7e818b 100644
--- a/frontend/farm_designer/map/layers/sensor_readings/sensor_readings_layer.tsx
+++ b/frontend/farm_designer/map/layers/sensor_readings/sensor_readings_layer.tsx
@@ -7,9 +7,23 @@ import { GardenSensorReading } from "./garden_sensor_reading";
import { last, round } from "lodash";
import { TimeSettings } from "../../../../interfaces";
import {
- fetchInterpolationOptions, generateData, InterpolationMap,
+ fetchInterpolationOptions, generateData, GetColor, InterpolationMap,
} from "../points/interpolation_map";
+export const filterMoistureReadings = (
+ sensorReadings: TaggedSensorReading[],
+ sensors: TaggedSensor[],
+) => {
+ const sensorNameByPinLookup: { [x: number]: string } = {};
+ sensors.map(x => { sensorNameByPinLookup[x.body.pin || 0] = x.body.label; });
+ const readings = sensorReadings
+ .filter(r =>
+ (sensorNameByPinLookup[r.body.pin] || "").toLowerCase().includes("soil")
+ && r.body.mode == ANALOG)
+ .filter(r => r.body.value <= 900);
+ return { readings, sensorNameByPinLookup };
+};
+
export interface SensorReadingsLayerProps {
visible: boolean;
overlayVisible: boolean;
@@ -25,16 +39,14 @@ export function SensorReadingsLayer(props: SensorReadingsLayerProps) {
visible, sensorReadings, mapTransformProps, timeSettings, sensors
} = props;
const mostRecentSensorReading = last(sensorReadings);
- const sensorNameByPinLookup: { [x: number]: string } = {};
- sensors.map(x => { sensorNameByPinLookup[x.body.pin || 0] = x.body.label; });
const options = fetchInterpolationOptions(props.farmwareEnvs);
- const moistureReadings = sensorReadings
- .filter(r =>
- (sensorNameByPinLookup[r.body.pin] || "").toLowerCase().includes("soil")
- && r.body.mode == ANALOG);
+ const { readings: moistureReadings, sensorNameByPinLookup } =
+ filterMoistureReadings(sensorReadings, sensors);
generateData({
kind: "SensorReading",
- points: moistureReadings, mapTransformProps, getColor: getMoistureColor,
+ points: moistureReadings,
+ gridSize: mapTransformProps.gridSize,
+ getColor: getMoistureColor,
options,
});
return
@@ -57,8 +69,15 @@ export function SensorReadingsLayer(props: SensorReadingsLayerProps) {
;
}
-const getMoistureColor = (value: number) => {
- const normalizedValue = round(255 * value / 1024);
- if (value > 900) { return "rgb(255, 255, 255)"; }
- return `rgb(0, 0, ${normalizedValue})`;
+export const getMoistureColor: GetColor = (value: number) => {
+ const maxValue = 900;
+ if (value > maxValue) { return { rgb: "rgb(0, 0, 0)", a: 0 }; }
+ const r = 0;
+ const g = 0;
+ const b = 255;
+ const a = round((0.75 * value / maxValue) ** 3, 2);
+ return {
+ rgb: `rgb(${r}, ${g}, ${b})`,
+ a: a,
+ };
};
diff --git a/frontend/farm_designer/map/legend/__tests__/garden_map_legend_test.tsx b/frontend/farm_designer/map/legend/__tests__/garden_map_legend_test.tsx
index 64c074df3b..d1975542d8 100644
--- a/frontend/farm_designer/map/legend/__tests__/garden_map_legend_test.tsx
+++ b/frontend/farm_designer/map/legend/__tests__/garden_map_legend_test.tsx
@@ -45,7 +45,6 @@ describe("", () => {
showZones: false,
showSensorReadings: false,
showMoistureInterpolationMap: false,
- hasSensorReadings: false,
dispatch: jest.fn(),
timeSettings: fakeTimeSettings(),
getConfigValue: jest.fn(),
@@ -69,7 +68,6 @@ describe("", () => {
it("renders with readings", () => {
const p = fakeProps();
- p.hasSensorReadings = true;
const wrapper = mount();
expect(wrapper.text().toLowerCase()).toContain("readings");
});
diff --git a/frontend/farm_designer/map/legend/garden_map_legend.tsx b/frontend/farm_designer/map/legend/garden_map_legend.tsx
index 2f61fd7262..ee188a874a 100644
--- a/frontend/farm_designer/map/legend/garden_map_legend.tsx
+++ b/frontend/farm_designer/map/legend/garden_map_legend.tsx
@@ -129,8 +129,8 @@ interface LayerTogglesProps extends GardenMapLegendProps { }
const LayerToggles = (props: LayerTogglesProps) => {
const { toggle, getConfigValue, dispatch, firmwareConfig } = props;
const subMenuProps = { dispatch, getConfigValue, firmwareConfig };
- const only2DClass =
- getConfigValue(BooleanSetting.three_d_garden) ? "disabled" : "";
+ const is3D = getConfigValue(BooleanSetting.three_d_garden);
+ const only2DClass = is3D ? "disabled" : "";
return
{
value={props.showPoints}
label={DeviceSetting.showPoints}
onClick={toggle(BooleanSetting.show_points)} />
-
+ {!is3D &&
+ }
{
value={props.showZones}
label={DeviceSetting.showAreas}
onClick={toggle(BooleanSetting.show_zones)} />
- {props.hasSensorReadings &&
- }
- {props.hasSensorReadings &&
- }
+
+
;
};
diff --git a/frontend/farm_designer/three_d_garden_map.tsx b/frontend/farm_designer/three_d_garden_map.tsx
index f47d8e6ba9..358977643b 100644
--- a/frontend/farm_designer/three_d_garden_map.tsx
+++ b/frontend/farm_designer/three_d_garden_map.tsx
@@ -7,8 +7,9 @@ import {
import { clone } from "lodash";
import { BotPosition, SourceFbosConfig } from "../devices/interfaces";
import {
- ConfigurationName, TaggedCurve, TaggedGenericPointer, TaggedImage, TaggedPoint,
- TaggedPointGroup, TaggedWeedPointer,
+ ConfigurationName, TaggedCurve, TaggedFarmwareEnv, TaggedGenericPointer,
+ TaggedImage, TaggedPoint,
+ TaggedPointGroup, TaggedSensor, TaggedSensorReading, TaggedWeedPointer,
} from "farmbot";
import { CameraCalibrationData, DesignerState } from "./interfaces";
import { GetWebAppConfigValue } from "../config_storage/actions";
@@ -22,6 +23,7 @@ import { DeviceAccountSettings } from "farmbot/dist/resources/api_resources";
import { SCENES } from "../settings/three_d_settings";
import { get3DTime, latLng } from "../three_d_garden/time_travel";
import { parseCalibrationData } from "./map/layers/images/map_image";
+import { fetchInterpolationOptions } from "./map/layers/points/interpolation_map";
export interface ThreeDGardenMapProps {
botSize: BotSize;
@@ -45,7 +47,10 @@ export interface ThreeDGardenMapProps {
allPoints: TaggedPoint[];
groups: TaggedPointGroup[];
images: TaggedImage[];
+ sensorReadings: TaggedSensorReading[];
+ sensors: TaggedSensor[];
cameraCalibrationData: CameraCalibrationData;
+ farmwareEnvs: TaggedFarmwareEnv[];
}
export const ThreeDGardenMap = (props: ThreeDGardenMapProps) => {
@@ -104,7 +109,8 @@ export const ThreeDGardenMap = (props: ThreeDGardenMapProps) => {
config.eventDebug = !!getValue("eventDebug");
config.cableDebug = !!getValue("cableDebug");
config.lightsDebug = !!getValue("lightsDebug");
- config.surfaceDebug = !!getValue("surfaceDebug");
+ config.moistureDebug = !!getValue("moistureDebug");
+ config.surfaceDebug = getValue("surfaceDebug");
config.sun = getValue("sun");
config.ambient = getValue("ambient");
config.heading = getValue("heading");
@@ -159,6 +165,11 @@ export const ThreeDGardenMap = (props: ThreeDGardenMapProps) => {
config.imgCenterX = camCalData.centerX;
config.imgCenterY = camCalData.centerY;
+ const options = fetchInterpolationOptions(props.farmwareEnvs);
+ config.interpolationStepSize = options.stepSize;
+ config.interpolationUseNearest = options.useNearest;
+ config.interpolationPower = options.power;
+
config.zoom = true;
config.pan = true;
config.rotate = !props.designer.threeDTopDownView;
@@ -176,6 +187,8 @@ export const ThreeDGardenMap = (props: ThreeDGardenMapProps) => {
allPoints={props.allPoints}
groups={props.groups}
images={props.images}
+ sensorReadings={props.sensorReadings}
+ sensors={props.sensors}
addPlantProps={{
gridSize: props.mapTransformProps.gridSize,
dispatch: props.dispatch,
diff --git a/frontend/points/__tests__/soil_height_test.tsx b/frontend/points/__tests__/soil_height_test.tsx
index bbd62e4741..090bdf097b 100644
--- a/frontend/points/__tests__/soil_height_test.tsx
+++ b/frontend/points/__tests__/soil_height_test.tsx
@@ -42,7 +42,7 @@ describe("getSoilHeightColor()", () => {
tagAsSoilHeight(point1);
point1.body.z = 100;
const getColor = getSoilHeightColor([point0, point1]);
- expect(getColor(50)).toEqual("rgb(128, 128, 128)");
+ expect(getColor(50).rgb).toEqual("rgb(128, 128, 128)");
});
});
diff --git a/frontend/points/point_inventory.tsx b/frontend/points/point_inventory.tsx
index e181c29b98..638959cc0e 100644
--- a/frontend/points/point_inventory.tsx
+++ b/frontend/points/point_inventory.tsx
@@ -40,6 +40,7 @@ import { pointGroupSubset } from "../plants/select_plants";
import { Path } from "../internal_urls";
import { deleteAllIds } from "../api/delete_points_handler";
import { NavigationContext } from "../routes_helpers";
+import { GetColor } from "../farm_designer/map/layers/points/interpolation_map";
interface PointsSectionProps {
title: string;
@@ -52,7 +53,7 @@ interface PointsSectionProps {
hoveredPoint: UUID | undefined;
dispatch: Function;
metaQuery: Record;
- getColorOverride?(z: number): string;
+ getColorOverride?: GetColor;
averageZ?: number;
sourceFbosConfig?: SourceFbosConfig;
}
@@ -89,7 +90,7 @@ const PointsSection = (props: PointsSectionProps) => {
{genericPoints.map(p => )}
diff --git a/frontend/points/soil_height.tsx b/frontend/points/soil_height.tsx
index 0791159a92..36209084fd 100644
--- a/frontend/points/soil_height.tsx
+++ b/frontend/points/soil_height.tsx
@@ -45,7 +45,10 @@ export const getSoilHeightColor =
const max = Math.max(...soilHeights);
return (z: number) => {
const normalizedZ = round(255 * (max > min ? (z - min) / (max - min) : 1));
- return `rgb(${normalizedZ}, ${normalizedZ}, ${normalizedZ})`;
+ return {
+ rgb: `rgb(${normalizedZ}, ${normalizedZ}, ${normalizedZ})`,
+ a: 1,
+ };
};
};
diff --git a/frontend/sensors/sensor_readings/__tests__/sensor_readings_test.tsx b/frontend/sensors/sensor_readings/__tests__/sensor_readings_test.tsx
index 45d3682d13..4b64ef31f3 100644
--- a/frontend/sensors/sensor_readings/__tests__/sensor_readings_test.tsx
+++ b/frontend/sensors/sensor_readings/__tests__/sensor_readings_test.tsx
@@ -1,3 +1,7 @@
+jest.mock("../../../api/crud", () => ({
+ destroy: jest.fn(),
+}));
+
import React from "react";
import { mount } from "enzyme";
import moment from "moment";
@@ -7,6 +11,8 @@ import {
fakeSensorReading, fakeSensor,
} from "../../../__test_support__/fake_state/resources";
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
+import { destroy } from "../../../api/crud";
+import { busy } from "../../../toast/toast";
describe("", () => {
const fakeProps = (): SensorReadingsProps => ({
@@ -98,4 +104,29 @@ describe("", () => {
expect(wrapper.instance().state.xyzLocation).toEqual(undefined);
expect(wrapper.instance().state.sensor).toEqual(undefined);
});
+
+ it("deletes selected readings", () => {
+ jest.useFakeTimers();
+ window.confirm = () => true;
+ const p = fakeProps();
+ const wrapper = mount();
+ const reading = fakeSensorReading();
+ reading.uuid = "uuid0";
+ wrapper.instance().deleteSelected([reading])();
+ jest.runAllTimers();
+ expect(destroy).toHaveBeenCalledWith("uuid0");
+ expect(busy).toHaveBeenCalledWith("Deleting 1 sensor readings...");
+ });
+
+ it("doesn't delete selected readings", () => {
+ jest.useFakeTimers();
+ window.confirm = () => false;
+ const p = fakeProps();
+ const wrapper = mount();
+ const reading = fakeSensorReading();
+ reading.uuid = "uuid0";
+ wrapper.instance().deleteSelected([reading])();
+ jest.runAllTimers();
+ expect(destroy).not.toHaveBeenCalled();
+ });
});
diff --git a/frontend/sensors/sensor_readings/__tests__/table_test.tsx b/frontend/sensors/sensor_readings/__tests__/table_test.tsx
index 33e1abb232..36f139b711 100644
--- a/frontend/sensors/sensor_readings/__tests__/table_test.tsx
+++ b/frontend/sensors/sensor_readings/__tests__/table_test.tsx
@@ -1,3 +1,7 @@
+jest.mock("../../../api/crud", () => ({
+ destroy: jest.fn(),
+}));
+
import React from "react";
import { mount } from "enzyme";
import { SensorReadingsTable } from "../table";
@@ -6,6 +10,7 @@ import {
fakeSensorReading, fakeSensor,
} from "../../../__test_support__/fake_state/resources";
import { fakeTimeSettings } from "../../../__test_support__/fake_time_settings";
+import { destroy } from "../../../api/crud";
describe("", () => {
const fakeProps = (sr = fakeSensorReading()): SensorReadingsTableProps => ({
@@ -14,6 +19,7 @@ describe("", () => {
timeSettings: fakeTimeSettings(),
hover: jest.fn(),
hovered: undefined,
+ dispatch: jest.fn(),
});
it("renders", () => {
@@ -68,4 +74,14 @@ describe("", () => {
const wrapper = mount();
expect(wrapper.find("tr").last().hasClass("selected")).toEqual(true);
});
+
+ it("deletes reading", () => {
+ const sr = fakeSensorReading();
+ const p = fakeProps(sr);
+ p.hovered = sr.uuid;
+ const wrapper = mount();
+ expect(wrapper.find("tr").last().hasClass("selected")).toEqual(true);
+ wrapper.find(".fa-trash").first().simulate("click");
+ expect(destroy).toHaveBeenCalledWith(sr.uuid);
+ });
});
diff --git a/frontend/sensors/sensor_readings/interfaces.ts b/frontend/sensors/sensor_readings/interfaces.ts
index 610c08bfa4..b515dcf8c5 100644
--- a/frontend/sensors/sensor_readings/interfaces.ts
+++ b/frontend/sensors/sensor_readings/interfaces.ts
@@ -34,6 +34,7 @@ export interface SensorReadingsTableProps {
/** TaggedSensorReading UUID */
hovered: string | undefined;
hover: (hovered: string | undefined) => void;
+ dispatch: Function;
}
export interface TableRowProps {
@@ -46,6 +47,7 @@ export interface TableRowProps {
hover: (hovered: string | undefined) => void;
hideLocation?: boolean;
distance?: number;
+ dispatch: Function;
}
export interface SensorSelectionProps {
diff --git a/frontend/sensors/sensor_readings/sensor_readings.tsx b/frontend/sensors/sensor_readings/sensor_readings.tsx
index 674779e94d..938e1f04a3 100644
--- a/frontend/sensors/sensor_readings/sensor_readings.tsx
+++ b/frontend/sensors/sensor_readings/sensor_readings.tsx
@@ -9,11 +9,13 @@ import {
} from "./time_period_selection";
import { LocationSelection, LocationDisplay } from "./location_selection";
import { SensorSelection } from "./sensor_selection";
-import { TaggedSensor } from "farmbot";
+import { TaggedSensor, TaggedSensorReading } from "farmbot";
import { AxisInputBoxGroupState } from "../../controls/interfaces";
import { SensorReadingsPlot } from "./graph";
import { Position } from "@blueprintjs/core";
import { AddSensorReadingMenu } from "./add_reading";
+import { destroy } from "../../api/crud";
+import { busy } from "../../toast/toast";
export class SensorReadings
extends React.Component {
@@ -46,6 +48,15 @@ export class SensorReadings
showPreviousPeriod: false,
deviation: 0,
});
+ deleteSelected = (readings: TaggedSensorReading[]) => () => {
+ if (!confirm(t("Delete {{count}} sensor readings?", {
+ count: readings.length,
+ }))) { return; }
+ busy(t("Deleting {{count}} sensor readings...", { count: readings.length }));
+ readings.map((reading, index) => {
+ setTimeout(() => this.props.dispatch(destroy(reading.uuid)), index * 250);
+ });
+ };
toggleAddReadingMenu = () => {
this.setState({ addReadingMenuOpen: !this.state.addReadingMenuOpen });
@@ -61,6 +72,11 @@ export class SensorReadings
{t("History")}
+