update to the bin player to set the bin bushels in the status using the measurements from the bin measurements
This commit is contained in:
parent
939a53d072
commit
7666fbf9b6
2 changed files with 79 additions and 17 deletions
|
|
@ -8,7 +8,7 @@ import { Bin } from "models";
|
|||
import moment, { Moment } from "moment";
|
||||
import { pond } from "protobuf-ts/pond";
|
||||
import { quack } from "protobuf-ts/quack";
|
||||
import { useComponentAPI } from "providers";
|
||||
import { useBinAPI, useComponentAPI } from "providers";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
const CHECKPOINT_COUNT = 10;
|
||||
|
|
@ -16,6 +16,7 @@ const CHECKPOINT_COUNT = 10;
|
|||
interface StatusCheckpoint {
|
||||
time: Moment;
|
||||
timeString: string;
|
||||
statusBushels: number;
|
||||
cables: pond.GrainCable[];
|
||||
fans: pond.BinFan[];
|
||||
heaters: pond.BinHeater[];
|
||||
|
|
@ -99,11 +100,6 @@ type TopNodeHistoryEntry = {
|
|||
topNode: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts raw `ComponentHistory` entries for one cable into a sorted (ascending) array of
|
||||
* `TopNodeHistoryEntry` records, keeping only entries where `grain_filled_to` is defined and
|
||||
* non-zero so that we never overwrite a known top node with a "not set" value.
|
||||
*/
|
||||
function buildTopNodeHistory(
|
||||
history: pond.ComponentHistory[]
|
||||
): TopNodeHistoryEntry[] {
|
||||
|
|
@ -116,11 +112,6 @@ function buildTopNodeHistory(
|
|||
.sort((a, b) => a.timestamp.valueOf() - b.timestamp.valueOf());
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary-searches a sorted `TopNodeHistoryEntry[]` for the most recent entry whose timestamp
|
||||
* is at or before `checkpointTime`. Returns `undefined` when no such entry exists (i.e. the
|
||||
* checkpoint is before the first recorded top-node change).
|
||||
*/
|
||||
function topNodeAtTime(
|
||||
history: TopNodeHistoryEntry[],
|
||||
checkpointTime: Moment
|
||||
|
|
@ -142,6 +133,61 @@ function topNodeAtTime(
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* A single recorded bushel estimate with its timestamp, sourced from bin object measurements.
|
||||
*/
|
||||
type BushelEntry = {
|
||||
timestamp: Moment;
|
||||
bushels: number;
|
||||
};
|
||||
|
||||
function buildBushelHistory(
|
||||
response: pond.ListObjectMeasurementsResponse
|
||||
): BushelEntry[] {
|
||||
const entries: BushelEntry[] = [];
|
||||
|
||||
response.measurements.forEach(um => {
|
||||
const isBushelType =
|
||||
um.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BUSHEL_LIDAR ||
|
||||
um.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BUSHEL_CABLE ||
|
||||
um.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_BUSHEL_LIBRACART;
|
||||
|
||||
if (!isBushelType) return;
|
||||
|
||||
um.timestamps.forEach((ts, i) => {
|
||||
const valueArray = um.values[i];
|
||||
if (!ts || !valueArray || valueArray.error || valueArray.values.length === 0) return;
|
||||
entries.push({
|
||||
timestamp: moment(ts),
|
||||
bushels: valueArray.values[0],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return entries.sort((a, b) => a.timestamp.valueOf() - b.timestamp.valueOf());
|
||||
}
|
||||
|
||||
function bushelsAtTime(
|
||||
history: BushelEntry[],
|
||||
checkpointTime: Moment
|
||||
): number | undefined {
|
||||
if (history.length === 0) return undefined;
|
||||
const t = checkpointTime.valueOf();
|
||||
let lo = 0;
|
||||
let hi = history.length - 1;
|
||||
let result: number | undefined = undefined;
|
||||
while (lo <= hi) {
|
||||
const mid = (lo + hi) >>> 1;
|
||||
if (history[mid].timestamp.valueOf() <= t) {
|
||||
result = history[mid].bushels;
|
||||
lo = mid + 1;
|
||||
} else {
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces `count` wall-clock times evenly spaced from `start` through `end` (inclusive).
|
||||
* These are the playback frames — not derived from measurement data.
|
||||
|
|
@ -414,6 +460,10 @@ function buildStatusCheckpoints(
|
|||
heaterMeasurementResponses: pond.SampleUnitMeasurementsResponse[],
|
||||
/** Sorted top-node history per cable key. Pass an empty Map when not needed. */
|
||||
topNodeHistories: Map<string, TopNodeHistoryEntry[]>,
|
||||
/** Sorted bushel history derived from bin object measurements. */
|
||||
bushelHistory: BushelEntry[],
|
||||
/** Fallback bushel count used when no measurement precedes a checkpoint. */
|
||||
fallbackBushels: number,
|
||||
start: Moment,
|
||||
end: Moment,
|
||||
count: number = CHECKPOINT_COUNT
|
||||
|
|
@ -503,7 +553,12 @@ function buildStatusCheckpoints(
|
|||
return buildBinHeaterFromState(template, merged);
|
||||
});
|
||||
|
||||
return { time, cables, fans, heaters, timeString: time.toISOString() };
|
||||
// Resolve bushels: binary search gives the most recent recorded value at or before this
|
||||
// checkpoint (carry-forward is implicit in the search). Falls back to the current live
|
||||
// status bushels when the playback range predates all recorded measurements.
|
||||
const statusBushels = bushelsAtTime(bushelHistory, time) ?? fallbackBushels;
|
||||
|
||||
return { time, cables, fans, heaters, timeString: time.toISOString(), statusBushels };
|
||||
}).filter(checkpoint =>
|
||||
// Keep checkpoints that have at least some cable data
|
||||
checkpoint.cables.some(cable =>
|
||||
|
|
@ -537,6 +592,7 @@ export default function BinPlayer(props: Props) {
|
|||
const { bin, componentDevices, setBin } = props;
|
||||
const isMobile = useMobile();
|
||||
const componentAPI = useComponentAPI();
|
||||
const binAPI = useBinAPI();
|
||||
const [dateSelect, setDateSelect] = useState<number>(0);
|
||||
const [startDate, setStartDate] = useState<Moment>(moment().subtract(1, 'week'));
|
||||
const [endDate, setEndDate] = useState<Moment>(moment);
|
||||
|
|
@ -598,7 +654,6 @@ export default function BinPlayer(props: Props) {
|
|||
// Cable top-node history requests — one per cable, covering the full playback range.
|
||||
const cableHistoryRequests = bin.status.grainCables.map(cable => {
|
||||
const deviceID = componentDevices.get(cable.key) ?? 0;
|
||||
console.log(deviceID)
|
||||
return componentAPI.listHistoryBetween(
|
||||
deviceID,
|
||||
cable.key,
|
||||
|
|
@ -619,15 +674,16 @@ export default function BinPlayer(props: Props) {
|
|||
return componentAPI.sampleUnitMeasurements(deviceID, heater.key, startDate, endDate, 100, undefined, undefined, undefined, undefined, true);
|
||||
});
|
||||
|
||||
const [cableResponses, fanResponses, heaterResponses, cableHistoryResponses] = await Promise.all([
|
||||
const binMeasurementsRequest = binAPI.listBinMeasurements(bin.key(), startDate, endDate, 0, 0, 'asc');
|
||||
|
||||
const [cableResponses, fanResponses, heaterResponses, cableHistoryResponses, binMeasurementsResponse] = await Promise.all([
|
||||
Promise.all(cableSampleRequests),
|
||||
Promise.all(fanSampleRequests),
|
||||
Promise.all(heaterSampleRequests),
|
||||
Promise.all(cableHistoryRequests),
|
||||
binMeasurementsRequest,
|
||||
]);
|
||||
|
||||
console.log(cableHistoryResponses)
|
||||
|
||||
// Build a per-cable sorted top-node history map for use during checkpoint construction.
|
||||
const topNodeHistories = new Map<string, TopNodeHistoryEntry[]>();
|
||||
bin.status.grainCables.forEach((cable, i) => {
|
||||
|
|
@ -636,6 +692,9 @@ export default function BinPlayer(props: Props) {
|
|||
topNodeHistories.set(cable.key, entries);
|
||||
});
|
||||
|
||||
// Build sorted bushel history from bin object measurements.
|
||||
const bushelHistory = buildBushelHistory(binMeasurementsResponse.data);
|
||||
|
||||
const checkpoints = buildStatusCheckpoints(
|
||||
bin.status.grainCables,
|
||||
bin.status.fans,
|
||||
|
|
@ -644,6 +703,8 @@ export default function BinPlayer(props: Props) {
|
|||
fanResponses.map(r => r.data),
|
||||
heaterResponses.map(r => r.data),
|
||||
topNodeHistories,
|
||||
bushelHistory,
|
||||
bin.status.grainBushels,
|
||||
startDate,
|
||||
endDate,
|
||||
checkpointCountFromRange(startDate, endDate)
|
||||
|
|
@ -664,6 +725,7 @@ export default function BinPlayer(props: Props) {
|
|||
newBin.status.grainCables = checkpoints[i].cables;
|
||||
newBin.status.fans = checkpoints[i].fans;
|
||||
newBin.status.heaters = checkpoints[i].heaters;
|
||||
newBin.status.grainBushels = checkpoints[i].statusBushels;
|
||||
|
||||
setBin(newBin);
|
||||
setCurrentCheckpointTime(checkpoints[i].time);
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ export default function BinTableView(props: Props) {
|
|||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ background: theme.palette.background.default}}
|
||||
sx={{ background: theme.palette.background.default, color: theme.palette.text.primary}}
|
||||
onClick={() => {
|
||||
setOpenExpandedCables(true)
|
||||
}}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue