From 46e4e6f5f8548f1152b684ff2e4a88786a316256 Mon Sep 17 00:00:00 2001 From: Joseph Weller Date: Sat, 30 Mar 2024 12:40:59 -0700 Subject: [PATCH 1/4] webcam working --- src/api/v1/README.md | 5 +++-- src/api/v1/api.py | 4 ++-- src/api/v1/requirements.txt | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/api/v1/README.md b/src/api/v1/README.md index 0e34f4e..2d48274 100644 --- a/src/api/v1/README.md +++ b/src/api/v1/README.md @@ -11,10 +11,11 @@ python3.11 -m venv venv ``` From here, you can source your venv with the following commands. -Windows, probably: + +Windows: ``` -./venv/bin/Activate.ps1 +.\venv\Scripts\Activate.ps1 ``` Linux: diff --git a/src/api/v1/api.py b/src/api/v1/api.py index 43250c8..ee45117 100644 --- a/src/api/v1/api.py +++ b/src/api/v1/api.py @@ -6,7 +6,7 @@ import cv2 app = FastAPI() -camera = cv2.VideoCapture(0, cv2.CAP_DSHOW) +camera = cv2.VideoCapture(1, cv2.CAP_DSHOW) templates = Jinja2Templates(directory="templates") @@ -32,6 +32,6 @@ async def get_stream(websocket: WebSocket): else: ret, buffer = cv2.imencode(".jpg", frame) await websocket.send_bytes(buffer.tobytes()) - await asyncio.sleep(0.03) + await asyncio.sleep(0.01) except (WebSocketDisconnect, ConnectionClosed): print("Client disconnected") diff --git a/src/api/v1/requirements.txt b/src/api/v1/requirements.txt index 430616e..e426d82 100644 --- a/src/api/v1/requirements.txt +++ b/src/api/v1/requirements.txt @@ -17,6 +17,6 @@ sniffio==1.3.1 starlette==0.36.3 typing_extensions==4.10.0 uvicorn==0.29.0 -uvloop==0.19.0 +# uvloop==0.19.0 watchfiles==0.21.0 websockets==12.0 From 36180b83ceca4b26dadfee92231e7198444eac92 Mon Sep 17 00:00:00 2001 From: Joseph Weller Date: Sat, 30 Mar 2024 14:50:41 -0700 Subject: [PATCH 2/4] contols --- src/api/v1/api.py | 29 ++++++--- src/ui/v1/src/app/page.tsx | 116 +++++++++++++++++++++++++++++++++-- src/ui/v1/tailwind.config.ts | 10 +++ 3 files changed, 140 insertions(+), 15 deletions(-) diff --git a/src/api/v1/api.py b/src/api/v1/api.py index ee45117..a216151 100644 --- a/src/api/v1/api.py +++ b/src/api/v1/api.py @@ -1,7 +1,9 @@ from typing import Union +import json from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect from websockets.exceptions import ConnectionClosed from fastapi.templating import Jinja2Templates +from fastapi.middleware.cors import CORSMiddleware import asyncio import cv2 @@ -9,16 +11,17 @@ camera = cv2.VideoCapture(1, cv2.CAP_DSHOW) templates = Jinja2Templates(directory="templates") +origins = [ + "http://localhost:3000", +] -@app.get("/") -def read_root(): - return {"Hello": "World"} - - -@app.get("/items/{item_id}") -def read_item(item_id: int, q: Union[str, None] = None): - return {"item_id": item_id, "q": q} - +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], # Allow all HTTP methods + allow_headers=["*"], # Allow all headers +) # https://stackoverflow.com/a/70626324 @app.websocket("/ws") @@ -32,6 +35,12 @@ async def get_stream(websocket: WebSocket): else: ret, buffer = cv2.imencode(".jpg", frame) await websocket.send_bytes(buffer.tobytes()) - await asyncio.sleep(0.01) + await asyncio.sleep(0.03) except (WebSocketDisconnect, ConnectionClosed): print("Client disconnected") + +@app.post("/command") +async def command(request: Request): + data = await request.json() + data_dict = json.loads(data) + return {"status": "success"} \ No newline at end of file diff --git a/src/ui/v1/src/app/page.tsx b/src/ui/v1/src/app/page.tsx index 94533e1..c7fcaf1 100644 --- a/src/ui/v1/src/app/page.tsx +++ b/src/ui/v1/src/app/page.tsx @@ -1,12 +1,16 @@ "use client" -import { useRef, useEffect, useInsertionEffect } from "react"; +import { useRef, useEffect, useInsertionEffect, useState } from "react"; export default function Home() { const ws = new WebSocket('ws://localhost:8000/ws'); const canvasRef = useRef(null); + const [left, setLeft] = useState(0); + const [up, setUp] = useState(0); + const [right, setRight] = useState(0); + const [down, setDown] = useState(0); + useInsertionEffect(() => { - ws.onmessage = (event) => { if (event.data instanceof Blob) { const reader = new FileReader(); @@ -27,7 +31,6 @@ export default function Home() { } }; - // Clean up WebSocket connection on component unmount return () => { ws.close(); }; @@ -37,11 +40,114 @@ export default function Home() { const canvas = canvasRef.current; const context = canvas?.getContext('2d'); }, []); - + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + switch (event.key) { + case "ArrowUp": + setUp(1); + break; + case "ArrowDown": + setDown(1); + break; + case "ArrowLeft": + setLeft(1); + break; + case "ArrowRight": + setRight(1); + break; + default: + break; + } + }; + + document.addEventListener("keydown", handleKeyDown); + + return () => { + document.removeEventListener("keydown", handleKeyDown); + }; + }, []); + + useEffect(() => { + const handleKeyUp = (event: KeyboardEvent) => { + switch (event.key) { + case "ArrowUp": + setUp(0); + break; + case "ArrowDown": + setDown(0); + break; + case "ArrowLeft": + setLeft(0); + break; + case "ArrowRight": + setRight(0); + break; + default: + break; + } + } + + document.addEventListener("keyup", handleKeyUp); + + return () => { + document.removeEventListener("keyup", handleKeyUp); + } + }, []) + + useEffect(() => { + if (left === 1 || up === 1 || right === 1 || down === 1) { + fetch('http://localhost:8000/command', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + left, + up, + right, + down + }), + }); + } + }, [down, left, right, up]) + return (
- +
+
+ +
+
+
+ + + + +
+
+
); diff --git a/src/ui/v1/tailwind.config.ts b/src/ui/v1/tailwind.config.ts index e9a0944..3964007 100644 --- a/src/ui/v1/tailwind.config.ts +++ b/src/ui/v1/tailwind.config.ts @@ -14,6 +14,16 @@ const config: Config = { "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", }, }, + colors: { + red: "#ee6352", + green: "#59cd90", + blue: "#3fa7d6", + yellow: "#fac05e", + purple: "#b678e0", + black: "#1e1e1e", + white: "#f9f9f9", + orange: "#f79d84" + } }, plugins: [], }; From add2fd04cfb4dd469483d3927e680a03eadae729 Mon Sep 17 00:00:00 2001 From: Joseph Weller Date: Sat, 30 Mar 2024 16:26:38 -0700 Subject: [PATCH 3/4] control UI & bugfix --- src/ui/v1/package-lock.json | 11 ++++++- src/ui/v1/package.json | 9 +++--- src/ui/v1/src/app/globals.css | 6 +++- src/ui/v1/src/app/page.tsx | 58 +++++++++++++++++++++++++---------- 4 files changed, 62 insertions(+), 22 deletions(-) diff --git a/src/ui/v1/package-lock.json b/src/ui/v1/package-lock.json index 8251d86..2a41a91 100644 --- a/src/ui/v1/package-lock.json +++ b/src/ui/v1/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "next": "14.1.4", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "react-icons": "^5.0.1" }, "devDependencies": { "@types/node": "^20", @@ -3836,6 +3837,14 @@ "react": "^18.2.0" } }, + "node_modules/react-icons": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz", + "integrity": "sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/src/ui/v1/package.json b/src/ui/v1/package.json index 160abe9..f31621f 100644 --- a/src/ui/v1/package.json +++ b/src/ui/v1/package.json @@ -9,19 +9,20 @@ "lint": "next lint" }, "dependencies": { + "next": "14.1.4", "react": "^18", "react-dom": "^18", - "next": "14.1.4" + "react-icons": "^5.0.1" }, "devDependencies": { - "typescript": "^5", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10.0.1", + "eslint": "^8", + "eslint-config-next": "14.1.4", "postcss": "^8", "tailwindcss": "^3.3.0", - "eslint": "^8", - "eslint-config-next": "14.1.4" + "typescript": "^5" } } diff --git a/src/ui/v1/src/app/globals.css b/src/ui/v1/src/app/globals.css index bd6213e..7882a8e 100644 --- a/src/ui/v1/src/app/globals.css +++ b/src/ui/v1/src/app/globals.css @@ -1,3 +1,7 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +.text-transparent { + color: transparent !important; +} \ No newline at end of file diff --git a/src/ui/v1/src/app/page.tsx b/src/ui/v1/src/app/page.tsx index c7fcaf1..f0711fa 100644 --- a/src/ui/v1/src/app/page.tsx +++ b/src/ui/v1/src/app/page.tsx @@ -1,5 +1,6 @@ "use client" import { useRef, useEffect, useInsertionEffect, useState } from "react"; +import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp } from "react-icons/fa"; export default function Home() { const ws = new WebSocket('ws://localhost:8000/ws'); @@ -96,20 +97,18 @@ export default function Home() { }, []) useEffect(() => { - if (left === 1 || up === 1 || right === 1 || down === 1) { - fetch('http://localhost:8000/command', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - left, - up, - right, - down - }), - }); - } + fetch('http://localhost:8000/command', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + left, + up, + right, + down + }), + }); }, [down, left, right, up]) return ( @@ -121,7 +120,34 @@ export default function Home() {
- + */}
From c24c4c68dce8176513ff5acf72c18257d9c51655 Mon Sep 17 00:00:00 2001 From: Joseph Weller Date: Sat, 30 Mar 2024 17:23:30 -0700 Subject: [PATCH 4/4] formatting --- MODULE.bazel.lock | 2 +- src/api/v1/api.py | 7 ++++--- src/ui/v1/src/app/page.tsx | 24 ------------------------ 3 files changed, 5 insertions(+), 28 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 5b95294..6c6c56b 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,6 +1,6 @@ { "lockFileVersion": 6, - "moduleFileHash": "9116b2d8fb0ce39849813bdeb4be5ffcf1ad8eabf88914f56b198ba6b8ec515d", + "moduleFileHash": "4fa47c1d36ebfbfef45d2813f94b4ba9f3e8ebfef3d74978a732860e3c6b9f37", "flags": { "cmdRegistries": [ "https://bcr.bazel.build/" diff --git a/src/api/v1/api.py b/src/api/v1/api.py index a216151..ce7e322 100644 --- a/src/api/v1/api.py +++ b/src/api/v1/api.py @@ -1,5 +1,4 @@ from typing import Union -import json from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect from websockets.exceptions import ConnectionClosed from fastapi.templating import Jinja2Templates @@ -23,6 +22,7 @@ allow_headers=["*"], # Allow all headers ) + # https://stackoverflow.com/a/70626324 @app.websocket("/ws") async def get_stream(websocket: WebSocket): @@ -39,8 +39,9 @@ async def get_stream(websocket: WebSocket): except (WebSocketDisconnect, ConnectionClosed): print("Client disconnected") + @app.post("/command") async def command(request: Request): data = await request.json() - data_dict = json.loads(data) - return {"status": "success"} \ No newline at end of file + print(data) + return {"status": "success"} diff --git a/src/ui/v1/src/app/page.tsx b/src/ui/v1/src/app/page.tsx index f0711fa..0513516 100644 --- a/src/ui/v1/src/app/page.tsx +++ b/src/ui/v1/src/app/page.tsx @@ -147,30 +147,6 @@ export default function Home() {
- {/* - - - */}