From 96c44e65bdc1281b7dde04e052fffab2499c53d6 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 10 Apr 2024 09:16:46 -0500 Subject: [PATCH] better motion tuner --- web/src/api/ws.tsx | 14 ++ web/src/components/settings/MotionTuner.tsx | 243 +++++++++++++++++--- web/src/components/settings/Zones.tsx | 1 - 3 files changed, 223 insertions(+), 35 deletions(-) diff --git a/web/src/api/ws.tsx b/web/src/api/ws.tsx index 362a98749..ae885d10a 100644 --- a/web/src/api/ws.tsx +++ b/web/src/api/ws.tsx @@ -234,3 +234,17 @@ export function useMotionContourArea(camera: string): { ); return { payload: payload as string, send }; } + +export function useImproveContrast(camera: string): { + payload: string; + send: (payload: string, retain?: boolean) => void; +} { + const { + value: { payload }, + send, + } = useWs( + `${camera}/improve_contrast/state`, + `${camera}/improve_contrast/set`, + ); + return { payload: payload as string, send }; +} diff --git a/web/src/components/settings/MotionTuner.tsx b/web/src/components/settings/MotionTuner.tsx index 70efbe883..e1af979cb 100644 --- a/web/src/components/settings/MotionTuner.tsx +++ b/web/src/components/settings/MotionTuner.tsx @@ -8,18 +8,47 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "../ui/alert-dialog"; import { FrigateConfig } from "@/types/frigateConfig"; import useSWR from "swr"; +import axios from "axios"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import AutoUpdatingCameraImage from "@/components/camera/AutoUpdatingCameraImage"; -import { useCallback, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { Slider } from "@/components/ui/slider"; import { Label } from "@/components/ui/label"; -import { useMotionContourArea, useMotionThreshold } from "@/api/ws"; +import { + useImproveContrast, + useMotionContourArea, + useMotionThreshold, +} from "@/api/ws"; import { Skeleton } from "../ui/skeleton"; +import { Button } from "../ui/button"; +import { Switch } from "../ui/switch"; +import { Toaster } from "@/components/ui/sonner"; +import { toast } from "sonner"; + +type MotionSettings = { + threshold?: number; + contour_area?: number; + improve_contrast?: boolean; +}; export default function MotionTuner() { - const { data: config } = useSWR("config"); + const { data: config, mutate: updateConfig } = + useSWR("config"); + const [changedValue, setChangedValue] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false); const cameras = useMemo(() => { if (!config) { @@ -31,7 +60,18 @@ export default function MotionTuner() { .sort((aConf, bConf) => aConf.ui.order - bConf.ui.order); }, [config]); - const [selectedCamera, setSelectedCamera] = useState(cameras[0].name); + const [selectedCamera, setSelectedCamera] = useState(cameras[0]?.name); + const [nextSelectedCamera, setNextSelectedCamera] = useState(""); + + const { send: sendMotionThreshold } = useMotionThreshold(selectedCamera); + const { send: sendMotionContourArea } = useMotionContourArea(selectedCamera); + const { send: sendImproveContrast } = useImproveContrast(selectedCamera); + + const [motionSettings, setMotionSettings] = useState({ + threshold: undefined, + contour_area: undefined, + improve_contrast: undefined, + }); const cameraConfig = useMemo(() => { if (config && selectedCamera) { @@ -39,36 +79,114 @@ export default function MotionTuner() { } }, [config, selectedCamera]); - const motionThreshold = useMemo(() => { - return cameraConfig?.motion.threshold ?? 0; - }, [cameraConfig?.motion.threshold]); + useEffect(() => { + if (cameraConfig) { + setMotionSettings({ + threshold: cameraConfig.motion.threshold, + contour_area: cameraConfig.motion.contour_area, + improve_contrast: cameraConfig.motion.improve_contrast, + }); + } + }, [cameraConfig]); - const motionContourArea = useMemo( - () => cameraConfig?.motion.contour_area ?? 0, - [cameraConfig?.motion.contour_area], - ); + useEffect(() => { + if (cameraConfig) { + const { threshold, contour_area, improve_contrast } = motionSettings; - const { send: sendMotionThreshold } = useMotionThreshold(selectedCamera); - const { send: sendMotionContourArea } = useMotionContourArea(selectedCamera); - - const setMotionThreshold = useCallback( - (threshold: number) => { - if (cameraConfig && threshold != motionThreshold) { - cameraConfig.motion.threshold = threshold; + if ( + threshold !== undefined && + cameraConfig.motion.threshold !== threshold + ) { sendMotionThreshold(threshold); } - }, - [cameraConfig, motionThreshold, sendMotionThreshold], - ); - const setMotionContourArea = useCallback( - (contour_area: number) => { - if (cameraConfig && contour_area != motionContourArea) { - cameraConfig.motion.contour_area = contour_area; + if ( + contour_area !== undefined && + cameraConfig.motion.contour_area !== contour_area + ) { sendMotionContourArea(contour_area); } + + if ( + improve_contrast !== undefined && + cameraConfig.motion.improve_contrast !== improve_contrast + ) { + sendImproveContrast(improve_contrast ? "ON" : "OFF"); + } + } + }, [ + cameraConfig, + motionSettings, + sendMotionThreshold, + sendMotionContourArea, + sendImproveContrast, + ]); + + const handleMotionConfigChange = (newConfig: Partial) => { + setMotionSettings((prevConfig) => ({ ...prevConfig, ...newConfig })); + setChangedValue(true); + }; + + const saveToConfig = useCallback(async () => { + setIsLoading(true); + + axios + .put( + `config/set?cameras.${selectedCamera}.motion.threshold=${motionSettings.threshold}&cameras.${selectedCamera}.motion.contour_area=${motionSettings.contour_area}&cameras.${selectedCamera}.motion.improve_contrast=${motionSettings.improve_contrast}`, + { requires_restart: 0 }, + ) + .then((res) => { + if (res.status === 200) { + toast.success("Motion settings saved.", { position: "top-center" }); + setChangedValue(false); + updateConfig(); + } else { + toast.error(`Failed to save config changes: ${res.statusText}`, { + position: "top-center", + }); + } + }) + .catch((error) => { + toast.error( + `Failed to save config changes: ${error.response.data.message}`, + { position: "top-center" }, + ); + }) + .finally(() => { + setIsLoading(false); + }); + }, [ + updateConfig, + motionSettings.threshold, + motionSettings.contour_area, + motionSettings.improve_contrast, + selectedCamera, + ]); + + const handleSelectedCameraChange = useCallback( + (camera: string) => { + if (changedValue) { + setNextSelectedCamera(camera); + setConfirmationDialogOpen(true); + } else { + setSelectedCamera(camera); + setNextSelectedCamera(""); + } }, - [cameraConfig, motionContourArea, sendMotionContourArea], + [setSelectedCamera, changedValue], + ); + + const handleDialog = useCallback( + (save: boolean) => { + if (save) { + saveToConfig(); + } + setSelectedCamera(nextSelectedCamera); + setNextSelectedCamera(""); + setConfirmationDialogOpen(false); + setChangedValue(false); + }, + [saveToConfig, setSelectedCamera, nextSelectedCamera], ); if (!cameraConfig && !selectedCamera) { @@ -78,8 +196,12 @@ export default function MotionTuner() { return ( <> Motion Detection Tuner +
- @@ -89,7 +211,7 @@ export default function MotionTuner() { {cameras.map((camera) => ( {camera.name} @@ -111,31 +233,84 @@ export default function MotionTuner() { setMotionThreshold(value[0])} + onValueChange={(value) => { + handleMotionConfigChange({ threshold: value[0] }); + }} />
setMotionContourArea(value[0])} + onValueChange={(value) => { + handleMotionConfigChange({ contour_area: value[0] }); + }} />
+
+ { + handleMotionConfigChange({ improve_contrast: isChecked }); + }} + /> + +
+ +
+ +
+ {confirmationDialogOpen && ( + setConfirmationDialogOpen(false)} + > + + + + You have unsaved changes on this camera. + + + Do you want to save your changes before continuing? + + + + handleDialog(false)}> + Cancel + + handleDialog(true)}> + Save + + + + + )} ) : ( diff --git a/web/src/components/settings/Zones.tsx b/web/src/components/settings/Zones.tsx index 955dd6032..15ad7a29b 100644 --- a/web/src/components/settings/Zones.tsx +++ b/web/src/components/settings/Zones.tsx @@ -226,7 +226,6 @@ export default function SettingsZones() { - {" "}
setActivePolygonIndex(index)}