Skip to content

Commit a966c5f

Browse files
authored
Merge pull request #207 from tritonuas/fix/tick-polling
Update Fetching Behavior
2 parents 5833062 + 6a1cec9 commit a966c5f

7 files changed

Lines changed: 195 additions & 109 deletions

File tree

.github/workflows/linter.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ jobs:
3434

3535
- name: golangci-lint
3636
uses: golangci/golangci-lint-action@v7
37+
with:
38+
version: v2.8.0
3739

3840
eslint:
3941
name: Lint Frontend

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ install-linter:
2222
fi;\
2323

2424
install-protos:
25-
sudo apt install protobuf-compiler
25+
sudo apt-get update && sudo apt-get install -y protobuf-compiler
2626
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
2727
echo "You will need to set your PATH variable to include go installations, if you have not already done so."
2828

houston/src/App.tsx

Lines changed: 80 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -46,59 +46,94 @@ function App() {
4646
const [coordinate, setCoordinate] = useState<LatLng[]>([]);
4747

4848
useEffect(() => {
49-
const interval = setInterval(() => {
50-
fetch("/api/plane/telemetry?id=33&field=lat,lon,alt,relative_alt")
49+
let isCancelled = false;
50+
let planeTimeoutId: number | null = null;
51+
let statusTimeoutId: number | null = null;
52+
53+
const pollPlaneTelemetry = async () => {
54+
await fetch("/api/plane/telemetry?id=33&field=lat,lon,alt,relative_alt")
5155
.then((resp) => resp.json())
5256
.then((json) => {
5357
const lat = parseFloat(json["lat"]) / 10e6;
5458
const lng = parseFloat(json["lon"]) / 10e6;
55-
setPlaneLatLng([lat, lng]);
59+
if (!isCancelled) {
60+
setPlaneLatLng([lat, lng]);
61+
}
62+
})
63+
.catch((error) => {
64+
console.error("Error fetching plane telemetry:", error);
5665
});
57-
}, 500);
5866

59-
const interval_long = setInterval(() => {
60-
// I hate this but don't feel motivated to refactor it, sorry ~Tyler 4/13/24
61-
fetch("/api/connections")
62-
.then((resp) => resp.json())
63-
.then((json) => {
64-
setStatuses((old) =>
65-
old.map((status: ConnectionStatus) => {
66-
switch (status.name) {
67-
case "Antenna Tracker":
68-
return {
69-
isActive: json["antenna_tracker"],
70-
type: status.type,
71-
name: status.name,
72-
} as ConnectionStatus;
73-
case "Onboard Computer":
74-
return {
75-
isActive: json["plane_obc"],
76-
type: status.type,
77-
name: status.name,
78-
} as ConnectionStatus;
79-
case "Radio Mavlink":
80-
return {
81-
isActive: json["radio_mavlink"],
82-
type: status.type,
83-
name: status.name,
84-
} as ConnectionStatus;
85-
default:
86-
return {} as ConnectionStatus;
87-
}
88-
}),
89-
);
90-
});
91-
fetch("/api/obc_connection")
92-
.then((resp) => resp.json())
93-
.then((json) => {
94-
// these keys are defined in the /connections route of the backend
95-
localStorage.setItem("obc_conn_status", JSON.stringify(json));
96-
});
97-
}, 2000);
67+
if (!isCancelled) {
68+
planeTimeoutId = window.setTimeout(() => {
69+
void pollPlaneTelemetry();
70+
}, 500);
71+
}
72+
};
73+
74+
const pollConnectionStatus = async () => {
75+
await Promise.allSettled([
76+
fetch("/api/connections")
77+
.then((resp) => resp.json())
78+
.then((json) => {
79+
if (isCancelled) {
80+
return;
81+
}
82+
setStatuses((old) =>
83+
old.map((status: ConnectionStatus) => {
84+
switch (status.name) {
85+
case "Antenna Tracker":
86+
return {
87+
isActive: json["antenna_tracker"],
88+
type: status.type,
89+
name: status.name,
90+
} as ConnectionStatus;
91+
case "Onboard Computer":
92+
return {
93+
isActive: json["plane_obc"],
94+
type: status.type,
95+
name: status.name,
96+
} as ConnectionStatus;
97+
case "Radio Mavlink":
98+
return {
99+
isActive: json["radio_mavlink"],
100+
type: status.type,
101+
name: status.name,
102+
} as ConnectionStatus;
103+
default:
104+
return {} as ConnectionStatus;
105+
}
106+
}),
107+
);
108+
}),
109+
fetch("/api/obc_connection")
110+
.then((resp) => resp.json())
111+
.then((json) => {
112+
if (!isCancelled) {
113+
// these keys are defined in the /connections route of the backend
114+
localStorage.setItem("obc_conn_status", JSON.stringify(json));
115+
}
116+
}),
117+
]);
118+
119+
if (!isCancelled) {
120+
statusTimeoutId = window.setTimeout(() => {
121+
void pollConnectionStatus();
122+
}, 2000);
123+
}
124+
};
125+
126+
void pollPlaneTelemetry();
127+
void pollConnectionStatus();
98128

99129
return () => {
100-
clearInterval(interval);
101-
clearInterval(interval_long);
130+
isCancelled = true;
131+
if (planeTimeoutId !== null) {
132+
window.clearTimeout(planeTimeoutId);
133+
}
134+
if (statusTimeoutId !== null) {
135+
window.clearTimeout(statusTimeoutId);
136+
}
102137
};
103138
}, []);
104139

houston/src/pages/Control.tsx

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -320,55 +320,89 @@ function Control({
320320

321321
const [tickState, setTickState] = useState<string>("Loading...");
322322

323-
// --- NEW useEffect specifically for fetching tick state ---
323+
// useEffect for fetching tick state
324324
useEffect(() => {
325-
const fetchTickState = () => {
326-
fetch("/api/tickstate")
327-
.then((response) => {
328-
if (!response.ok) {
325+
let isCancelled = false;
326+
let tickTimeoutId: number | null = null;
327+
let tickRequestInFlight = false;
328+
329+
const pollTickState = async () => {
330+
if (tickRequestInFlight || isCancelled) {
331+
return;
332+
}
333+
334+
tickRequestInFlight = true;
335+
try {
336+
const response = await fetch("/api/tickstate");
337+
if (!response.ok) {
338+
if (!isCancelled) {
329339
setTickState(`Error: ${response.status}`);
330-
throw new Error(`Tickstate fetch failed: ${response.status}`);
331340
}
332-
return response.text();
333-
})
334-
.then((data) => {
341+
throw new Error(`Tickstate fetch failed: ${response.status}`);
342+
}
343+
344+
const data = await response.text();
345+
if (!isCancelled) {
335346
setTickState(data);
336-
})
337-
.catch((error) => {
338-
console.error("Error fetching tick state:", error);
347+
}
348+
} catch (error) {
349+
console.error("Error fetching tick state:", error);
350+
if (!isCancelled) {
339351
setTickState("Error");
340-
});
352+
}
353+
} finally {
354+
tickRequestInFlight = false;
355+
if (!isCancelled) {
356+
tickTimeoutId = window.setTimeout(() => {
357+
void pollTickState();
358+
}, 3000);
359+
}
360+
}
341361
};
342362

343-
fetchTickState(); // Fetch immediately on component mount
344-
const tickInterval = setInterval(fetchTickState, 1000); // Then fetch every second
363+
void pollTickState();
345364

346365
return () => {
347-
clearInterval(tickInterval); // Cleanup interval on component unmount
366+
isCancelled = true;
367+
if (tickTimeoutId !== null) {
368+
window.clearTimeout(tickTimeoutId);
369+
}
348370
};
349-
}, []); // Empty dependency array ensures this runs once on mount and cleans up on unmount
371+
}, []);
350372

351373
useEffect(() => {
352-
const interval = setInterval(
353-
() =>
354-
pullTelemetry(
355-
settings,
356-
setPlaneLatLng,
357-
setAirspeed,
358-
setGroundspeed,
359-
setAltitudeMSL,
360-
setAltitudeAGL,
361-
setMotorBattery,
362-
setPixhawkBattery,
363-
setESCtemperature,
364-
setSuperSecret,
365-
setFlightMode,
366-
),
367-
1000,
368-
);
374+
let isCancelled = false;
375+
let telemetryTimeoutId: number | null = null;
376+
377+
const pollTelemetry = async () => {
378+
await pullTelemetry(
379+
settings,
380+
setPlaneLatLng,
381+
setAirspeed,
382+
setGroundspeed,
383+
setAltitudeMSL,
384+
setAltitudeAGL,
385+
setMotorBattery,
386+
setPixhawkBattery,
387+
setESCtemperature,
388+
setSuperSecret,
389+
setFlightMode,
390+
);
391+
392+
if (!isCancelled) {
393+
telemetryTimeoutId = window.setTimeout(() => {
394+
void pollTelemetry();
395+
}, 1000);
396+
}
397+
};
398+
399+
void pollTelemetry();
369400

370401
return () => {
371-
clearInterval(interval);
402+
isCancelled = true;
403+
if (telemetryTimeoutId !== null) {
404+
window.clearTimeout(telemetryTimeoutId);
405+
}
372406
};
373407
}, [settings]);
374408

houston/src/pages/Report.tsx

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ const Reports: React.FC = () => {
200200
const [manualInputError, setManualInputError] = useState<string>("");
201201

202202
// Refs
203-
const intervalIdRef = useRef<NodeJS.Timeout | null>(null);
203+
const pollingTimeoutRef = useRef<number | null>(null);
204204
const isMountedRef = useRef<boolean>(true);
205205
const imageContainerRef = useRef<HTMLDivElement>(null);
206206
const isFetchingTargetsRef = useRef<boolean>(false);
@@ -321,30 +321,34 @@ const Reports: React.FC = () => {
321321
return;
322322
}
323323

324-
const performInitialLiveFetch = async () => {
325-
if (isMountedRef.current) setIsPollingUI(true);
326-
await fetchAndProcessLatest(true);
327-
if (isMountedRef.current) setIsPollingUI(false);
328-
};
329-
330-
performInitialLiveFetch();
324+
let isCancelled = false;
325+
let localTimeoutId: number | null = null;
331326

332-
const localIntervalId = setInterval(async () => {
333-
if (!isMountedRef.current) {
334-
clearInterval(localIntervalId);
327+
const pollLatestRuns = async (isInitialFetch: boolean) => {
328+
if (!isMountedRef.current || isCancelled) {
335329
return;
336330
}
337-
if (isMountedRef.current) setIsPollingUI(true);
338-
await fetchAndProcessLatest(false);
339-
if (isMountedRef.current) setIsPollingUI(false);
340-
}, POLLING_INTERVAL_MS);
341-
intervalIdRef.current = localIntervalId;
331+
332+
setIsPollingUI(true);
333+
await fetchAndProcessLatest(isInitialFetch);
334+
335+
if (isMountedRef.current && !isCancelled) {
336+
setIsPollingUI(false);
337+
localTimeoutId = window.setTimeout(() => {
338+
void pollLatestRuns(false);
339+
}, POLLING_INTERVAL_MS);
340+
pollingTimeoutRef.current = localTimeoutId;
341+
}
342+
};
343+
344+
void pollLatestRuns(true);
342345

343346
return () => {
344-
if (localIntervalId) {
345-
clearInterval(localIntervalId);
347+
isCancelled = true;
348+
if (localTimeoutId !== null) {
349+
window.clearTimeout(localTimeoutId);
346350
}
347-
intervalIdRef.current = null;
351+
pollingTimeoutRef.current = null;
348352
};
349353
}, [hasLoadedInitialSavedRuns, fetchAndProcessLatest]);
350354

@@ -380,8 +384,8 @@ const Reports: React.FC = () => {
380384
isMountedRef.current = true;
381385
return () => {
382386
isMountedRef.current = false;
383-
if (intervalIdRef.current) {
384-
clearInterval(intervalIdRef.current);
387+
if (pollingTimeoutRef.current !== null) {
388+
window.clearTimeout(pollingTimeoutRef.current);
385389
}
386390
};
387391
}, []);

0 commit comments

Comments
 (0)