From 738f5d7414e9279e723d9771a7226ae1fa8f5600 Mon Sep 17 00:00:00 2001
From: jahugg <jan@huggenberg.ch>
Date: Wed, 22 Mar 2023 10:36:29 +0100
Subject: [PATCH] improved voice and motion detection

---
 src/main.js | 142 +++++++++++++++++++++++++++++-----------------------
 1 file changed, 80 insertions(+), 62 deletions(-)

diff --git a/src/main.js b/src/main.js
index 5547a40..85d6f1f 100644
--- a/src/main.js
+++ b/src/main.js
@@ -5,8 +5,8 @@ const config = {
   controls: {
     mouse: true,
     button: true,
-    voice: false,
-    motion: true,
+    voice: true,
+    motion: false,
   },
 };
 
@@ -47,9 +47,9 @@ const navigation = {
             },
           },
           check: {
-            label: "Check",
+            label: "Checklist",
             path: "/menu/check",
-            command: "check",
+            command: "checklist",
             submenu: {
               daily: {
                 label: "Daily",
@@ -367,6 +367,15 @@ function focusClosestItem(targetY = window.innerHeight / 2) {
   return focusNavigationItem(closestIndex);
 }
 
+// deselect all items
+function deselectAllItems() {
+  const navigationList = document.querySelectorAll("#navigation li");
+  for (let item of navigationList) {
+    item.querySelector(".progress").style.animation = "";
+    delete item.dataset.selected;
+  }
+}
+
 // moving up navigation tree
 function moveUpNavigationLevel() {
   const path = window.location.pathname;
@@ -403,14 +412,7 @@ function addMouseControls() {
   // unfocus all items
   appEl.addEventListener("mouseout", (event) => {
     const listItem = event.target.closest("li.link");
-    if (listItem) {
-      // unselect all items
-      const navigationList = document.querySelectorAll("#navigation li");
-      for (let item of navigationList) {
-        item.querySelector(".progress").style.animation = "";
-        delete item.dataset.selected;
-      }
-    }
+    if (listItem) deselectAllItems();
   });
 
   // select on mouseclick
@@ -527,13 +529,12 @@ function addVoiceControls() {
     SpeechRecognitionEvent || webkitSpeechRecognitionEvent;
 
   let availableCommands = [];
-  let commands = [];
-  updateVoiceCommandList();
+  let commands = getVoiceCommandList();
 
   // listen for menu updates and refresh commands list
   const navigationEl = document.getElementById("navigation");
   navigationEl.addEventListener("updated", (event) => {
-    updateVoiceCommandList();
+    commands = getVoiceCommandList();
   });
 
   let recognition = new SpeechRecognition();
@@ -557,12 +558,12 @@ function addVoiceControls() {
   recognition.onresult = function (event) {
     let command = event.results[0][0].transcript.toLowerCase();
     let confidence = event.results[0][0].confidence;
-    command = command.toLowerCase();
 
     console.log(`Voice > Received "${command}" (${confidence.toFixed(2)})`);
 
-    if (commands.includes(command)) {
-      let index = commands.indexOf(command); // get index of command in array
+    const matchingItem = commands.find(item => command.includes(item));
+    if (matchingItem) {
+      let index = commands.indexOf(matchingItem); // get index of command in array
       let path = availableCommands[index].path; // get corresponding path in availableCommands array
       navigateToPath(path);
     } else if (command.includes("select") || command.includes("okay"))
@@ -588,7 +589,7 @@ function addVoiceControls() {
   };
 
   // updating voice command list
-  function updateVoiceCommandList() {
+  function getVoiceCommandList() {
     // search and save currently available voice commands
     availableCommands = [];
     const listItems = document.querySelectorAll("a[data-command]");
@@ -601,7 +602,7 @@ function addVoiceControls() {
     }
 
     // form commands array for grammarlist
-    commands = availableCommands.map((x) => x.command);
+    return availableCommands.map((x) => x.command);
   }
 }
 
@@ -613,7 +614,8 @@ function addMotionControls() {
   let screenCenter = new Point(window.innerWidth / 2, window.innerHeight / 2);
   let idleTimeout;
   let intervalId;
-  let idleDelay = 3000; //idle time before reseting reference point
+  let intervalRunning = false;
+  let idleDelay = 5000; //idle time before reseting reference point
   let autoSelect = false; // selecting items with time delay
   let drawMotionFlag = true; // draw motion values for debugging
 
@@ -624,7 +626,7 @@ function addMotionControls() {
   const bufferIterationLimit = 5; // iterations before new average calculation is triggered
   const bufferLength = 10; // length of value buffer
   const noiseThreshold = 0.02; // threshold to avoid general sensor noise
-  const inactiveThreshold = 10; // threshold to define inactive area in the reference
+  const inactiveThreshold = 50; // threshold to define inactive area in the reference
 
   // create canvas element for motion visualization
   const canvasEl = document.createElement("canvas");
@@ -690,52 +692,62 @@ function addMotionControls() {
           // clear timeout monitoring if pointer is within reasonable distance
           if (distanceToReference < 400) {
             clearTimeout(idleTimeout);
-            clearInterval(intervalId);
 
             // reseting reference point after a delay of holding still
-            idleTimeout = setTimeout(() => {
-              printLogMsg("restart timer");
-              // start approaching new reference point
-              let incrementValue = 0.01;
-              intervalId = setInterval(() => {
-                if (referenceY < averageY) {
-                  printLogMsg("down");
-                  referenceY += incrementValue;
-                  if (referenceY >= averageY) {
-                    printLogMsg("clear interval down");
-                    referenceY = averageY; // Ensure exact match with target
-                    clearInterval(intervalId);
-                  }
-                } else if (referenceY > averageY) {
-                  printLogMsg("up");
-                  referenceY -= incrementValue;
-                  if (referenceY <= averageY) {
-                    printLogMsg("clear interval up");
-                    referenceY = averageY; // Ensure exact match with target
-                    clearInterval(intervalId);
-                  }
-                } else {
-                  printLogMsg("clear interval neutral");
-                  clearInterval(intervalId);
-                }
-              }, 100);
-              focusClosestItem(referenceY);
-            }, idleDelay);
+            if (!intervalRunning)
+              idleTimeout = setTimeout(() => {
+                printLogMsg("starting timer");
+                // start approaching new reference point
+                let incrementValue = 0.01;
+                intervalId = setInterval(() => {
+                  intervalRunning = true;
+                  if (referenceY < averageY) {
+                    printLogMsg("down");
+                    referenceY += incrementValue;
+                    if (referenceY >= averageY) {
+                      referenceY = averageY; // Ensure exact match with target
+                      stopInterval(intervalId);
+                    }
+                  } else if (referenceY > averageY) {
+                    printLogMsg("up");
+                    referenceY -= incrementValue;
+                    if (referenceY <= averageY) {
+                      referenceY = averageY; // Ensure exact match with target
+                      stopInterval(intervalId);
+                    }
+                  } else stopInterval(intervalId);
+                }, 100);
+              }, idleDelay);
           }
 
-          lastAverageY = averageY; // save averageY for later comparison
-
-          let itemEl = focusClosestItem(targetY);
-
-          if (autoSelect) {
-            let progressEl = itemEl.querySelector(".progress");
-            progressEl.style.animation = "progress 3s ease-out forwards";
+          // select item if ouside of center save area
+          if (distanceToReference > inactiveThreshold) {
+            let targetY = averageY;
+            targetY = (targetY - referenceY) * -sensitivity;
+            let itemEl = focusClosestItem(targetY);
+
+            // trigger autoselection via motion
+            if (autoSelect) {
+              let progressEl = itemEl.querySelector(".progress");
+              progressEl.style.animation = "progress 2s ease-out forwards";
+            }
+          } else {
+            printLogMsg("deselect all");
+            deselectAllItems();
           }
+
+          lastAverageY = averageY; // save averageY for later comparison
         }
         bufferIterations = 0;
       }
       bufferIterations++;
     }
+
+    function stopInterval(id) {
+      printLogMsg("interval stopped");
+      clearInterval(id);
+      intervalRunning = false;
+    }
   }
 
   function drawMotion(y) {
@@ -744,8 +756,6 @@ function addMotionControls() {
       const pointSize = 10;
 
       ctx.clearRect(0, 0, canvasEl.width, canvasEl.height);
-      ctx.strokeStyle = "rgba(255, 0, 255, .7)";
-      ctx.lineWidth = 5;
 
       ctx.beginPath();
       ctx.arc(
@@ -755,16 +765,24 @@ function addMotionControls() {
         0,
         2 * Math.PI
       );
+      ctx.strokeStyle = "rgba(255, 0, 255, .6)";
+      ctx.lineWidth = 3;
+      ctx.stroke();
+      ctx.closePath();
+
+      ctx.beginPath();
       ctx.moveTo(screenCenter.x, screenCenter.y);
       ctx.lineTo(screenCenter.x, screenCenter.y + y);
+      ctx.strokeStyle = "rgba(255, 0, 255, .6)";
+      ctx.lineWidth = 3;
       ctx.stroke();
       ctx.closePath();
 
       ctx.beginPath();
       ctx.arc(screenCenter.x, screenCenter.y + y, pointSize, 0, 2 * Math.PI);
+      ctx.fillStyle = "rgba(255, 0, 255, .6)";
       ctx.fill();
-      ctx.fillStyle = "rgb(255, 0, 255)";
-      ctx.stroke();
+      ctx.closePath();
     }
   }
 }
-- 
GitLab