diff --git a/src/app/components/Controller/Controller.tsx b/src/app/components/Controller/Controller.tsx index 38608f4..430a8cc 100644 --- a/src/app/components/Controller/Controller.tsx +++ b/src/app/components/Controller/Controller.tsx @@ -1,21 +1,21 @@ -"use client" +"use client"; -import React, { ChangeEvent, useEffect, useRef, useState } from "react" -import HotkeyButton from "../HotkeyButton/HotkeyButton" -import { useSerial } from "../SerialLoader/SerialLoader" -import { DataPacket } from "../SerialProvider/SerialProvider" -import ToggleSwitch from "../ToggleSwitch/ToggleSwitch" +import React, { ChangeEvent, useEffect, useRef, useState } from "react"; +import HotkeyButton from "../HotkeyButton/HotkeyButton"; +import { useSerial } from "../SerialLoader/SerialLoader"; +import { DataPacket } from "../SerialProvider/SerialProvider"; +import ToggleSwitch from "../ToggleSwitch/ToggleSwitch"; const Controller = () => { - const { serial, consoleMessage } = useSerial() - const [consoleMessageList, setConsoleMessageList] = useState("") - const [updateStatus, setUpdateStatus] = useState("") - const [command, setCommand] = useState("") - const [autoUpdateFrame, setAutoUpdateFrame] = useState(true) - const [loadingFrame, setLoadingFrame] = useState(true) - const canvasRef = useRef(null) + const { serial, consoleMessage } = useSerial(); + const [consoleMessageList, setConsoleMessageList] = useState(""); + const [updateStatus, setUpdateStatus] = useState(""); + const [command, setCommand] = useState(""); + const [autoUpdateFrame, setAutoUpdateFrame] = useState(true); + const [loadingFrame, setLoadingFrame] = useState(true); + const canvasRef = useRef(null); - const started = useRef(false) + const started = useRef(false); const write = async ( command: string, @@ -26,137 +26,137 @@ const Controller = () => { id: 0, command: "", response: null, - } - if (awaitResponse) data = await serial.queueWriteAndResponse(command) - else serial.queueWrite(command) + }; + if (awaitResponse) data = await serial.queueWriteAndResponse(command); + else serial.queueWrite(command); if (updateFrame) { - serial.queueWrite("screenframeshort") - setLoadingFrame(true) + serial.queueWrite("screenframeshort"); + setLoadingFrame(true); } - return data - } + return data; + }; const sendCommand = async () => { - await write(command, false) - setCommand("") - } + await write(command, false); + setCommand(""); + }; useEffect(() => { // We dont add this to the console as its not needed. This may change in the future if (consoleMessage.includes("screenframe")) { - renderFrame() - setLoadingFrame(false) + renderFrame(); + setLoadingFrame(false); } else { setConsoleMessageList( (prevConsoleMessageList) => prevConsoleMessageList + consoleMessage - ) + ); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [consoleMessage]) + }, [consoleMessage]); useEffect(() => { if (serial.isOpen && !serial.isReading && !started.current) { - started.current = true - serial.startReading() - write(setDeviceTime(), false) - write("screenframeshort", false) + started.current = true; + serial.startReading(); + write(setDeviceTime(), false); + write("screenframeshort", false); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [serial]) + }, [serial]); const renderFrame = () => { - const width = 241 - const height = 321 - if (!consoleMessage.includes("screenframe")) return + const width = 241; + const height = 321; + if (!consoleMessage.includes("screenframe")) return; - const lines = consoleMessage.split("\r\n") - const ctx = canvasRef.current?.getContext("2d") + const lines = consoleMessage.split("\r\n"); + const ctx = canvasRef.current?.getContext("2d"); - if (!ctx) return false + if (!ctx) return false; for (let y = 0; y < lines.length; y++) { - let line = lines[y] - if (line.startsWith("screenframe")) continue + let line = lines[y]; + if (line.startsWith("screenframe")) continue; for (let o = 0, x = 0; o < line.length && x < 240; o++, x++) { try { - let by = line.charCodeAt(o) - 32 - let r = ((by >> 4) & 3) << 6 - let g = ((by >> 2) & 3) << 6 - let b = (by & 3) << 6 + let by = line.charCodeAt(o) - 32; + let r = ((by >> 4) & 3) << 6; + let g = ((by >> 2) & 3) << 6; + let b = (by & 3) << 6; - ctx.fillStyle = `rgb(${r},${g},${b})` - ctx?.fillRect(x, y, 1, 1) + ctx.fillStyle = `rgb(${r},${g},${b})`; + ctx?.fillRect(x, y, 1, 1); } catch (err) { - console.error(err) + console.error(err); } } } - } + }; const setDeviceTime = () => { - const currentDateTime: Date = new Date() + const currentDateTime: Date = new Date(); // Add 3 seconds to the current time to account for the time it takes to send the command - currentDateTime.setSeconds(currentDateTime.getSeconds() + 3) - const year: number = currentDateTime.getFullYear() - let month: string | number = currentDateTime.getMonth() + 1 // JavaScript months are 0-11 - let day: string | number = currentDateTime.getDate() - let hours: string | number = currentDateTime.getHours() - let minutes: string | number = currentDateTime.getMinutes() - let seconds: string | number = currentDateTime.getSeconds() + currentDateTime.setSeconds(currentDateTime.getSeconds() + 3); + const year: number = currentDateTime.getFullYear(); + let month: string | number = currentDateTime.getMonth() + 1; // JavaScript months are 0-11 + let day: string | number = currentDateTime.getDate(); + let hours: string | number = currentDateTime.getHours(); + let minutes: string | number = currentDateTime.getMinutes(); + let seconds: string | number = currentDateTime.getSeconds(); // Making sure we have two digit representation - month = month < 10 ? "0" + month : month - day = day < 10 ? "0" + day : day - hours = hours < 10 ? "0" + hours : hours - minutes = minutes < 10 ? "0" + minutes : minutes - seconds = seconds < 10 ? "0" + seconds : seconds + month = month < 10 ? "0" + month : month; + day = day < 10 ? "0" + day : day; + hours = hours < 10 ? "0" + hours : hours; + minutes = minutes < 10 ? "0" + minutes : minutes; + seconds = seconds < 10 ? "0" + seconds : seconds; - return `rtcset ${year} ${month} ${day} ${hours} ${minutes} ${seconds}` - } + return `rtcset ${year} ${month} ${day} ${hours} ${minutes} ${seconds}`; + }; const handleKeyDown = (e: React.KeyboardEvent) => { if ( (e.key.length === 1 && /[a-zA-Z0-9 ]/.test(e.key)) || e.key === "Backspace" ) { - e.preventDefault() - let key_code = e.key.length === 1 ? e.key.charCodeAt(0) : e.keyCode - const keyHex = key_code.toString(16).padStart(2, "0").toUpperCase() - write(`keyboard ${keyHex}`, autoUpdateFrame) + e.preventDefault(); + let key_code = e.key.length === 1 ? e.key.charCodeAt(0) : e.keyCode; + const keyHex = key_code.toString(16).padStart(2, "0").toUpperCase(); + write(`keyboard ${keyHex}`, autoUpdateFrame); } - } + }; const uploadFile = async (filePath: string, bytes: Uint8Array) => { - await write("fclose", false) - await write(`fopen ${filePath}`, false) + await write("fclose", false); + await write(`fopen ${filePath}`, false); - await write(`fseek 0`, false) + await write(`fseek 0`, false); - let blob = new Blob([bytes]) - const arrayBuffer = await blob.arrayBuffer() + let blob = new Blob([bytes]); + const arrayBuffer = await blob.arrayBuffer(); - const chunkSize = 9000 + const chunkSize = 100000; - console.log("Total length: ", arrayBuffer.byteLength) + console.log("Total length: ", arrayBuffer.byteLength); - let startTime = Date.now() - let totalTime = 0 + let startTime = Date.now(); + let totalTime = 0; for (let i = 0; i < arrayBuffer.byteLength; i += chunkSize) { - const chunk = arrayBuffer.slice(i, i + chunkSize) + const chunk = arrayBuffer.slice(i, i + chunkSize); - await write(`fwb ${chunk.byteLength}`, false, true) - await serial.queueWriteAndResponseBinary(new Uint8Array(chunk)) + await write(`fwb ${chunk.byteLength}`, false, true); + await serial.queueWriteAndResponseBinary(new Uint8Array(chunk)); // calculate elapsed time and average time per chunk - let elapsed = Date.now() - startTime - totalTime += elapsed - let avgTimePerChunk = totalTime / (i / chunkSize + 1) + let elapsed = Date.now() - startTime; + totalTime += elapsed; + let avgTimePerChunk = totalTime / (i / chunkSize + 1); // estimate remaining time in seconds - let remainingChunks = (arrayBuffer.byteLength - i) / chunkSize - let estRemainingTime = (remainingChunks * avgTimePerChunk) / 1000 + let remainingChunks = (arrayBuffer.byteLength - i) / chunkSize; + let estRemainingTime = (remainingChunks * avgTimePerChunk) / 1000; console.log( "Chunk done", @@ -164,149 +164,154 @@ const Controller = () => { arrayBuffer.byteLength, ((i / arrayBuffer.byteLength) * 100).toFixed(2) + "%", "Estimated time remaining: " + estRemainingTime.toFixed(0) + " seconds" - ) + ); setUpdateStatus( `${((i / arrayBuffer.byteLength) * 100).toFixed( 2 )}% Estimated time remaining: ${estRemainingTime.toFixed(0)} seconds` - ) + ); // reset start time for next iteration - startTime = Date.now() + startTime = Date.now(); } - console.log("FILE DONE") + console.log("FILE DONE"); + setUpdateStatus(`File upload complete!`); - await write("fclose", false) - } + await write("fclose", false); + }; const downloadFile = async (filePath: string) => { - await write("fclose", false) - let sizeResponse = await write(`filesize ${filePath}`, false, true) + await write("fclose", false); + let sizeResponse = await write(`filesize ${filePath}`, false, true); if (!sizeResponse.response) { - console.error("Error downloading (size) file") + console.error("Error downloading (size) file"); } - let size = parseInt(sizeResponse.response?.split("\r\n")[1] || "0") - await write(`fopen ${filePath}`, false) + let size = parseInt(sizeResponse.response?.split("\r\n")[1] || "0"); + await write(`fopen ${filePath}`, false); - await write(`fseek 0`, false) + await write(`fseek 0`, false); - let rem = size - let chunk = 62 * 15 + let rem = size; + let chunk = 62 * 15; - let dataObject: Uint8Array = new Uint8Array() + let dataObject: Uint8Array = new Uint8Array(); while (rem > 0) { if (rem < chunk) { - chunk = rem + chunk = rem; } let lines = (await write(`fread ${chunk.toString()}`, false, true)).response ?.split("\r\n") .slice(1) .slice(0, -2) - .join("") || "" + .join("") || ""; - let bArr = hexToBytes(lines) - rem -= bArr.length - dataObject = new Uint8Array([...dataObject, ...Array.from(bArr)]) + let bArr = hexToBytes(lines); + rem -= bArr.length; + dataObject = new Uint8Array([...dataObject, ...Array.from(bArr)]); } downloadFileFromBytes( dataObject, filePath.substring(filePath.lastIndexOf("/") + 1) - ) - await write("fclose", false) - } + ); + await write("fclose", false); + }; const hexToBytes = (hex: string) => { - let bytes = new Uint8Array(Math.ceil(hex.length / 2)) + let bytes = new Uint8Array(Math.ceil(hex.length / 2)); for (let i = 0; i < bytes.length; i++) - bytes[i] = parseInt(hex.substr(i * 2, 2), 16) - return bytes - } + bytes[i] = parseInt(hex.substr(i * 2, 2), 16); + return bytes; + }; const bytesToHex = (bytes: Uint8Array) => { - return bytes.map((byte: any) => byte.toString(16).padStart(2, "0")).join("") - } + return bytes + .map((byte: any) => byte.toString(16).padStart(2, "0")) + .join(""); + }; const downloadFileFromBytes = ( bytes: Uint8Array | string, fileName: string = "output.txt" ) => { - let blob = new Blob([bytes]) - let url = URL.createObjectURL(blob) - let a = document.createElement("a") - a.style.display = "none" - a.href = url - a.download = fileName // Filename - document.body.appendChild(a) - a.click() - document.body.removeChild(a) - URL.revokeObjectURL(url) - } + let blob = new Blob([bytes]); + let url = URL.createObjectURL(blob); + let a = document.createElement("a"); + a.style.display = "none"; + a.href = url; + a.download = fileName; // Filename + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; const onFileChange = (event: ChangeEvent) => { - const fileList = event.target.files - if (!fileList) return + const fileList = event.target.files; + if (!fileList) return; - let file = fileList[0] - let reader = new FileReader() + let file = fileList[0]; + let reader = new FileReader(); reader.onloadend = () => { - const arrayBuffer = reader.result + const arrayBuffer = reader.result; if (arrayBuffer instanceof ArrayBuffer) { - let bytes = new Uint8Array(arrayBuffer) - uploadFile(file.name, bytes) + let bytes = new Uint8Array(arrayBuffer); + uploadFile(file.name, bytes); } - } + }; reader.onerror = () => { - console.error("A problem occurred while reading the file.") - } + console.error("A problem occurred while reading the file."); + }; if (file) { - reader.readAsArrayBuffer(file) + reader.readAsArrayBuffer(file); } - } + }; interface DownloadedFile { - blob: Blob - filename: string + blob: Blob; + filename: string; } const downloadFileFromUrl = async (url: string): Promise => { - const response = await fetch(url) + const response = await fetch(url); if (!response.ok) { - throw new Error("Network response was not ok") + throw new Error("Network response was not ok"); } - const contentDispositionHeader = response.headers.get("Content-Disposition") - const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/ - let matches = contentDispositionHeader?.match(filenameRegex) + const contentDispositionHeader = response.headers.get( + "Content-Disposition" + ); + const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/; + let matches = contentDispositionHeader?.match(filenameRegex); let filename = - matches && matches[1] ? matches[1].replace(/['"]/g, "") : "unknown.fail" + matches && matches[1] ? matches[1].replace(/['"]/g, "") : "unknown.fail"; - const blob = await response.blob() + const blob = await response.blob(); - return { blob, filename } - } + return { blob, filename }; + }; const flashLatestFirmware = async () => { const fileBlob = await downloadFileFromUrl( "https://hackrf.app/api/fetch_nightly_firmware" - ) + ); - console.log("Downloading firmware update...", fileBlob.filename) + console.log("Downloading firmware update...", fileBlob.filename); await uploadFile( `/FIRMWARE/${fileBlob.filename}`, new Uint8Array(await fileBlob.blob.arrayBuffer()) - ) + ); - await write(`flash /FIRMWARE/${fileBlob.filename}`, false, true) - console.log("DONE! firmware complete. Rebooting...") - alert("Firmware update complete! Please wait for your device to reboot.") - } + await write(`flash /FIRMWARE/${fileBlob.filename}`, false, true); + console.log("DONE! firmware complete. Rebooting..."); + alert("Firmware update complete! Please wait for your device to reboot."); + }; const handleScroll = (e: React.WheelEvent) => { // Disabled for the moment @@ -320,7 +325,7 @@ const Controller = () => { // // Add your scroll down Logic here // write("button 8", false) // } - } + }; return ( <> @@ -333,7 +338,7 @@ const Controller = () => { onWheel={handleScroll} tabIndex={0} onKeyDown={(e) => { - handleKeyDown(e) + handleKeyDown(e); }} >
{ { - if (!autoUpdateFrame) write("screenframeshort", false) - setAutoUpdateFrame(!autoUpdateFrame) + if (!autoUpdateFrame) write("screenframeshort", false); + setAutoUpdateFrame(!autoUpdateFrame); }} /> { disabled={loadingFrame} onClickFunction={() => { if (!loadingFrame) { - setLoadingFrame(true) - write("screenframeshort", false) + setLoadingFrame(true); + write("screenframeshort", false); } }} className="h-6 w-6 bg-blue-500" @@ -374,12 +379,12 @@ const Controller = () => { onMouseDown={( event: React.MouseEvent ) => { - if (!canvasRef.current) return - const bounds = canvasRef.current.getBoundingClientRect() - const x = event.clientX - bounds.left - const y = event.clientY - bounds.top + if (!canvasRef.current) return; + const bounds = canvasRef.current.getBoundingClientRect(); + const x = event.clientX - bounds.left; + const y = event.clientY - bounds.top; - write(`touch ${x} ${y}`, autoUpdateFrame) + write(`touch ${x} ${y}`, autoUpdateFrame); }} />
@@ -500,15 +505,15 @@ const Controller = () => { > Download */} - {/* */} + setCommand(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { - e.preventDefault() - sendCommand() + e.preventDefault(); + sendCommand(); } }} className="w-full rounded-md border-2 border-blue-500 p-2 text-black" @@ -517,7 +522,7 @@ const Controller = () => { type="submit" className="rounded-md bg-blue-500 p-2 text-white" onClick={() => { - sendCommand() + sendCommand(); }} > Send @@ -526,7 +531,7 @@ const Controller = () => { type="submit" className="rounded-md bg-red-500 p-2 text-white" onClick={() => { - setConsoleMessageList("") + setConsoleMessageList(""); }} > Clear @@ -542,8 +547,8 @@ const Controller = () => { )} - ) -} + ); +}; -export default Controller -7 +export default Controller; +7; diff --git a/src/app/components/SerialProvider/SerialProvider.tsx b/src/app/components/SerialProvider/SerialProvider.tsx index 9158a78..997de7b 100644 --- a/src/app/components/SerialProvider/SerialProvider.tsx +++ b/src/app/components/SerialProvider/SerialProvider.tsx @@ -377,17 +377,17 @@ const useWebSerial = ({ if (writer) { try { // Once speed is fixed, this can be swapped in for the loop below - // await writer.write(data); + await writer.write(data); - let blob = new Blob([data]); - const arrayBuffer = await blob.arrayBuffer(); - const chunkSize = 350; + // let blob = new Blob([data]); + // const arrayBuffer = await blob.arrayBuffer(); + // const chunkSize = 350; - for (let i = 0; i < arrayBuffer.byteLength; i += chunkSize) { - const chunk = arrayBuffer.slice(i, i + chunkSize); - await delay(5); - await writer.write(new Uint8Array(chunk)); - } + // for (let i = 0; i < arrayBuffer.byteLength; i += chunkSize) { + // const chunk = arrayBuffer.slice(i, i + chunkSize); + // await delay(5); + // await writer.write(new Uint8Array(chunk)); + // } writer.releaseLock(); setMessageQueue((prevQueue) => prevQueue.slice(1)); // Remove the message we just wrote from the queue @@ -457,10 +457,11 @@ const useWebSerial = ({ const queueWriteAndResponseBinary = async (message: Uint8Array) => { const id = commandCounter.current++; - const messageString = String.fromCharCode.apply( - null, - Array.from(new Uint8Array(message)) - ); + const messageString = "long msg"; + // const messageString = String.fromCharCode.apply( + // null, + // Array.from(new Uint8Array(message)) + // ); commandResponseMap.current = [ ...commandResponseMap.current, {