diff --git a/src/app/icons.tsx b/src/app/icons.tsx new file mode 100644 index 0000000..86c15f2 --- /dev/null +++ b/src/app/icons.tsx @@ -0,0 +1,80 @@ +"use client"; +import React from "react"; + +export function SpeakerOnIcon() { + return ( + + + + ); +} +export function SpeakerOffIcon() { + return ( + + + + ); +} +export function EyeIcon() { + return ( + + + + + ); +} +export function MegaphoneIcon() { + return ( + + + + ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 55eefbd..39f7f7b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,214 +1,67 @@ "use client"; -import React from "react"; +import React, { useCallback, useMemo } from "react"; import Image from "next/image"; -import { - TParsedDMMResponse, - debounceReadingFunc, - parseDMMBuffer, - startBluetoothCoonection as startBluetoothConnection, -} from "@/helpers/parseDMM"; +import { TParsedDMMResponse, parseDMMBuffer } from "@/helpers/parseDMM"; +import { debounceReadingFunc } from "@/helpers/debounce"; +import { BluetoothConnection } from "@/helpers/bluetooth"; import { startBuzz } from "@/helpers/buzzSound"; -import { - MutableRefObject, - useEffect, - useRef, - useState, - Dispatch, - SetStateAction, -} from "react"; +import { useEffect, useRef, useState } from "react"; +import { EyeIcon, MegaphoneIcon, SpeakerOnIcon, SpeakerOffIcon } from "./icons"; +import { useStateWithRef } from "@/helpers/custom-hooks"; -function SpeakerOnIcon() { - return ( - - - - ); +function useBluetoothConnection() { + let bluetoothConnectionRef = useRef(null); + if (bluetoothConnectionRef.current == null) { + bluetoothConnectionRef.current = new BluetoothConnection(); + } + const bluetoothConnection = bluetoothConnectionRef.current; + const { + startConnection, + startAutoReconnect, + addConnectHandler, + addDisconnectHandler, + addNotifyHandler, + disconnect, + enableShouldAutoReconnect, + disableShouldAutoReconnect, + } = bluetoothConnection; + return { + startConnection, + startAutoReconnect, + addConnectHandler, + addDisconnectHandler, + addNotifyHandler, + disconnect, + enableShouldAutoReconnect, + disableShouldAutoReconnect, + }; } -function SpeakerOffIcon() { - return ( - - - - ); -} - -function EyeIcon() { - return ( - - - - - ); -} - -function MegaphoneIcon() { - return ( - - - - ); -} - -function useBuzzSound() {} - -function useStateWithRef( - initialValue: TState, -): [TState, Dispatch>, MutableRefObject] { - const [value, setValue] = useState(initialValue); - const valueRef = useRef(initialValue); - valueRef.current = value; - - return [value, setValue, valueRef]; -} - -export default function Home() { - const [printedValue, setPrintedValue] = useState( - undefined, - ); - const [liveValue, setLiveValue] = useState(undefined); +function useSpeechApi() { const [isMute, setIsMute, isMuteRef] = useStateWithRef(false); - const stopBuzzRef = useRef<(() => void) | undefined>(undefined); - - let [selectedVoice, setSelectedVoice, selectedVoiceRef] = useStateWithRef< - string | undefined + let [selectedVoiceURI, setSelectedVoiceURI, selectedVoiceURIRef] = + useStateWithRef(undefined); + let [allVoices, setAllVoices, allVoicesRef] = useStateWithRef< + SpeechSynthesisVoice[] | undefined >(undefined); - let [allVoices, setAllVoices] = useState(); - let multimeterBluetoothDeviceRef = useRef( - undefined, + + const speak = useCallback( + function speak(value: string) { + let speech = new SpeechSynthesisUtterance(); + speech.lang = "en"; + speech.text = value; + speech.voice = + allVoicesRef.current?.find( + ({ voiceURI }) => voiceURI == selectedVoiceURIRef.current, + ) || null; + speech.volume = isMuteRef.current ? 0 : 1; + window.speechSynthesis.speak(speech); + }, + [isMuteRef, selectedVoiceURIRef, allVoicesRef], ); - let multimeterDisconnectRef = useRef<(() => void) | undefined>(undefined); - - const [showDisconnect, setShowDisconnect] = useState(false); - const showConnect = !showDisconnect; - - function resetStatusAfterDisconnect() { - if (multimeterBluetoothDeviceRef.current !== undefined) { - // Only announce once - speak("Multimeter disconnected"); - } - multimeterBluetoothDeviceRef.current = undefined; - multimeterDisconnectRef.current = undefined; - setLiveValue(undefined); - setPrintedValue(undefined); - setShowDisconnect(false); - } - - function speak(value: string) { - let speech = new SpeechSynthesisUtterance(); - speech.lang = "en"; - speech.text = value; - speech.voice = - allVoices?.find( - ({ voiceURI }) => voiceURI === selectedVoiceRef.current, - ) || null; - speech.volume = isMuteRef.current ? 0 : 1; - window.speechSynthesis.speak(speech); - } - - function handleConnectClick() { - const debouncedLogAndSpeak = debounceReadingFunc((value) => { - value = value as string; - setPrintedValue(value); - console.log(value); - speak(value.replace("-", "Minus ")); - }, 700); - - function handleBuzz(parsedDMMresponse: TParsedDMMResponse) { - const { isBuzz, isDiode } = parsedDMMresponse; - // If BUZZ sign & not DIODE sign & not mute - if (isBuzz && !isDiode && !isMuteRef.current) { - // Multimeter is buzzing, reciprocate - const { stop } = startBuzz(); - stopBuzzRef.current = stop; - } else { - stopBuzzRef.current?.(); - } - } - - function onNotify(bufferAsArray: number[]) { - const parsedDMMresponse = parseDMMBuffer(bufferAsArray); - const printValue = parsedDMMresponse.toString(); - // Set live value - setLiveValue(printValue); - - // Debounced announce value - debouncedLogAndSpeak(printValue); - - // Buzz, if needed - handleBuzz(parsedDMMresponse); - } - startBluetoothConnection({ - onDisconnect: () => resetStatusAfterDisconnect(), - onNotify, - }).then((obj) => { - multimeterBluetoothDeviceRef.current = obj.multimeterBluetoothDevice; - multimeterDisconnectRef.current = obj.disconnect; - setShowDisconnect(true); - speak("Connected"); - }); - } - - function handleDisconnectClick() { - if (multimeterDisconnectRef.current) { - console.log("Calling disconnect..."); - multimeterDisconnectRef.current(); - - resetStatusAfterDisconnect(); - } else { - console.log("Disconnect is not set"); - } - } + // Effect - Init all voices useEffect(() => { function _updateAllVoices() { // Get List of Voices @@ -220,7 +73,7 @@ export default function Home() { if (allEnglishVoices.length > 0) { // Initially set the First Voice in the Array. - setSelectedVoice(allEnglishVoices[0].name); + setSelectedVoiceURI(allEnglishVoices[0].voiceURI); } } @@ -228,7 +81,113 @@ export default function Home() { window.speechSynthesis.onvoiceschanged = () => { _updateAllVoices(); }; - }, [setSelectedVoice]); + }, [setSelectedVoiceURI, setAllVoices]); + + return { + allVoices, + selectedVoiceURI, + setSelectedVoiceURI, + isMute, + setIsMute, + speak, + }; +} + +export default function Home() { + const [printedValue, setPrintedValue] = useState( + undefined, + ); + const [liveValue, setLiveValue] = useState(undefined); + const stopBuzzRef = useRef<(() => void) | undefined>(undefined); + + const [showDisconnect, setShowDisconnect] = useState(false); + const showConnect = !showDisconnect; + + const { + startConnection, + startAutoReconnect, + addConnectHandler, + addDisconnectHandler, + addNotifyHandler, + disconnect, + enableShouldAutoReconnect, + disableShouldAutoReconnect, + } = useBluetoothConnection(); + const { + allVoices, + selectedVoiceURI, + setSelectedVoiceURI, + isMute, + setIsMute, + speak, + } = useSpeechApi(); + + const debouncedLogAndSpeak = useMemo( + () => + debounceReadingFunc((value) => { + value = value as string; + setPrintedValue(value); + console.log(value); + speak(value.replace("-", "Minus ")); + }, 700), + [speak], + ); + + function handleBuzz(parsedDMMresponse: TParsedDMMResponse) { + const { isBuzz, isDiode } = parsedDMMresponse; + // If BUZZ sign & not DIODE sign & not mute + if (isBuzz && !isDiode && !isMute) { + // Multimeter is buzzing, reciprocate + const { stop } = startBuzz(); + stopBuzzRef.current = stop; + } else { + stopBuzzRef.current?.(); + } + } + + function onNotify(bufferAsArray: number[]) { + const parsedDMMresponse = parseDMMBuffer(bufferAsArray); + const printValue = parsedDMMresponse.toString(); + // Set live value + setLiveValue(printValue); + + // Debounced announce value + debouncedLogAndSpeak(printValue); + + // Buzz, if needed + handleBuzz(parsedDMMresponse); + } + addNotifyHandler(onNotify); + + function onDisconnect() { + speak("Multimeter disconnected"); + + setLiveValue(undefined); + setPrintedValue(undefined); + setShowDisconnect(false); + + // Has internal check for shouldAutoReconnect + startAutoReconnect(); + } + addDisconnectHandler(onDisconnect); + + function onConnect() { + // TODO: Check if autoReconnect settings enabled + enableShouldAutoReconnect(); + setShowDisconnect(true); + speak("Connected"); + } + addConnectHandler(onConnect); + + function handleConnectClick() { + startConnection(); + } + + function handleDisconnectClick() { + console.log("Clicked disconnect"); + disableShouldAutoReconnect(); + disconnect(); + } function handleMuteClick() { setIsMute(true); @@ -294,8 +253,8 @@ export default function Home() {