feat: websocket server
feat: emulator (virtualcam) feat: basic web client
This commit is contained in:
parent
56d309e953
commit
7b7fbde1fe
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
roms/
|
roms/
|
||||||
.venv/
|
.venv/
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
|
states/
|
||||||
|
|
67
src/client.html
Normal file
67
src/client.html
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Telecommande</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<button id="a">a</button>
|
||||||
|
<button id="b">b</button>
|
||||||
|
<button id="select">select</button>
|
||||||
|
<button id="start">start</button>
|
||||||
|
<button id="right">right</button>
|
||||||
|
<button id="left">left</button>
|
||||||
|
<button id="up">up</button>
|
||||||
|
<button id="down">down</button>
|
||||||
|
<button id="r">r</button>
|
||||||
|
<button id="l">l</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var websocket = new WebSocket("ws://127.0.0.1:6789/");
|
||||||
|
|
||||||
|
var a = document.getElementById('a'),
|
||||||
|
b = document.getElementById('b'),
|
||||||
|
select = document.getElementById('select'),
|
||||||
|
start = document.getElementById('start'),
|
||||||
|
right = document.getElementById('right'),
|
||||||
|
left = document.getElementById('left'),
|
||||||
|
up = document.getElementById('up'),
|
||||||
|
down = document.getElementById('down'),
|
||||||
|
r = document.getElementById('r'),
|
||||||
|
l = document.getElementById('l');
|
||||||
|
|
||||||
|
a.onclick = function (event) {
|
||||||
|
websocket.send(JSON.stringify({ action: 'a' }));
|
||||||
|
}
|
||||||
|
b.onclick = function (event) {
|
||||||
|
websocket.send(JSON.stringify({ action: 'b' }));
|
||||||
|
}
|
||||||
|
select.onclick = function (event) {
|
||||||
|
websocket.send(JSON.stringify({ action: 'select' }));
|
||||||
|
}
|
||||||
|
start.onclick = function (event) {
|
||||||
|
websocket.send(JSON.stringify({ action: 'start' }));
|
||||||
|
}
|
||||||
|
right.onclick = function (event) {
|
||||||
|
websocket.send(JSON.stringify({ action: 'right' }));
|
||||||
|
}
|
||||||
|
left.onclick = function (event) {
|
||||||
|
websocket.send(JSON.stringify({ action: 'left' }));
|
||||||
|
}
|
||||||
|
up.onclick = function (event) {
|
||||||
|
websocket.send(JSON.stringify({ action: 'up' }));
|
||||||
|
}
|
||||||
|
down.onclick = function (event) {
|
||||||
|
websocket.send(JSON.stringify({ action: 'down' }));
|
||||||
|
}
|
||||||
|
r.onclick = function (event) {
|
||||||
|
websocket.send(JSON.stringify({ action: 'r' }));
|
||||||
|
}
|
||||||
|
l.onclick = function (event) {
|
||||||
|
websocket.send(JSON.stringify({ action: 'l' }));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
67
src/emulator.py
Normal file
67
src/emulator.py
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import mgba.core
|
||||||
|
import mgba.image
|
||||||
|
import mgba.log
|
||||||
|
import numpy as np
|
||||||
|
import pyvirtualcam
|
||||||
|
import websockets
|
||||||
|
|
||||||
|
WIDTH = 240
|
||||||
|
HEIGHT = 160
|
||||||
|
URI: str = "ws://127.0.0.1:6789/"
|
||||||
|
KEYMAP: dict[str, int] = {
|
||||||
|
"a": 0,
|
||||||
|
"b": 1,
|
||||||
|
"select": 2,
|
||||||
|
"start": 3,
|
||||||
|
"right": 4,
|
||||||
|
"left": 5,
|
||||||
|
"up": 6,
|
||||||
|
"down": 7,
|
||||||
|
"r": 8,
|
||||||
|
"l": 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
core = mgba.core.load_path("roms/pokemon.gba")
|
||||||
|
screen = mgba.image.Image(WIDTH, HEIGHT)
|
||||||
|
core.set_video_buffer(screen)
|
||||||
|
core.reset()
|
||||||
|
mgba.log.silence()
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
with pyvirtualcam.Camera(width=WIDTH, height=HEIGHT, fps=60) as cam:
|
||||||
|
logging.debug(f"Using virtual camera: {cam.device}")
|
||||||
|
async with websockets.connect(URI) as websocket:
|
||||||
|
await websocket.send('{"auth":"password"}')
|
||||||
|
logging.debug(f"connected to: {websocket}")
|
||||||
|
test = core.save_raw_state()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
if core.frame_counter % 30 == 0:
|
||||||
|
await websocket.send('{"action":"get"}')
|
||||||
|
response = await websocket.recv()
|
||||||
|
if response in KEYMAP:
|
||||||
|
key = KEYMAP[response]
|
||||||
|
core.set_keys(key)
|
||||||
|
logging.debug(f"pressing: {key}")
|
||||||
|
|
||||||
|
# with open("pokemon.state", "w") as f:
|
||||||
|
# f.write(test)
|
||||||
|
core.load_raw_state(test)
|
||||||
|
print(str(test))
|
||||||
|
|
||||||
|
core.run_frame()
|
||||||
|
core.clear_keys(*KEYMAP.values())
|
||||||
|
|
||||||
|
frame = np.array(screen.to_pil().convert("RGB"), np.uint8)
|
||||||
|
cam.send(frame)
|
||||||
|
cam.sleep_until_next_frame()
|
||||||
|
|
||||||
|
|
||||||
|
asyncio.run(main())
|
119
src/server.py
Normal file
119
src/server.py
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import websockets
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
class User:
|
||||||
|
websocket: Any = None
|
||||||
|
last_message: float = time.time()
|
||||||
|
has_voted: bool = False
|
||||||
|
|
||||||
|
def register(self, websocket: Any):
|
||||||
|
self.websocket = websocket
|
||||||
|
USERS.add(self)
|
||||||
|
logging.debug(
|
||||||
|
f"user registered: {self}",
|
||||||
|
)
|
||||||
|
|
||||||
|
def unregister(self):
|
||||||
|
# self.websocket.close()
|
||||||
|
USERS.remove(self)
|
||||||
|
logging.debug(
|
||||||
|
f"user unregistered: {self}",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.websocket.remote_address} ({self.websocket.id})"
|
||||||
|
|
||||||
|
|
||||||
|
USERS: set[User] = set()
|
||||||
|
EMULATOR: Any = None
|
||||||
|
PASSWORD: str = "password"
|
||||||
|
|
||||||
|
VOTES: dict[str, int] = {
|
||||||
|
"a": 0,
|
||||||
|
"b": 0,
|
||||||
|
"select": 0,
|
||||||
|
"start": 0,
|
||||||
|
"right": 0,
|
||||||
|
"left": 0,
|
||||||
|
"up": 0,
|
||||||
|
"down": 0,
|
||||||
|
"r": 0,
|
||||||
|
"l": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def clear_votes():
|
||||||
|
for key in VOTES.keys():
|
||||||
|
VOTES[key] = 0
|
||||||
|
for user in USERS:
|
||||||
|
user.has_voted = False
|
||||||
|
logging.info(f"votes cleared: {VOTES}")
|
||||||
|
|
||||||
|
|
||||||
|
def next_move():
|
||||||
|
if any(VOTES.values()):
|
||||||
|
return max(VOTES, key=VOTES.get)
|
||||||
|
else:
|
||||||
|
return "null"
|
||||||
|
|
||||||
|
|
||||||
|
async def handler(websocket, path):
|
||||||
|
try:
|
||||||
|
global EMULATOR
|
||||||
|
|
||||||
|
# Register user
|
||||||
|
user = User()
|
||||||
|
user.register(websocket)
|
||||||
|
|
||||||
|
# Manage received messages
|
||||||
|
async for message in websocket:
|
||||||
|
data = json.loads(message)
|
||||||
|
|
||||||
|
if "auth" in data:
|
||||||
|
auth: str = data["auth"]
|
||||||
|
logging.debug(f"auth received: {auth}")
|
||||||
|
if not EMULATOR and auth == PASSWORD:
|
||||||
|
EMULATOR = websocket
|
||||||
|
user.unregister()
|
||||||
|
logging.debug(f"emulator authenticated: {EMULATOR}")
|
||||||
|
|
||||||
|
if "action" in data:
|
||||||
|
action: str = data["action"]
|
||||||
|
|
||||||
|
if action in VOTES:
|
||||||
|
VOTES[action] += 1
|
||||||
|
user.last_message = time.time()
|
||||||
|
user.has_voted = True
|
||||||
|
logging.debug(f"key received: {action} ({VOTES[action]}), from {user}")
|
||||||
|
|
||||||
|
elif action == "get":
|
||||||
|
if EMULATOR and websocket == EMULATOR:
|
||||||
|
move = next_move()
|
||||||
|
await websocket.send(move)
|
||||||
|
logging.info(f"vote sent: {move}")
|
||||||
|
clear_votes()
|
||||||
|
else:
|
||||||
|
logging.error(f"user is not EMULATOR: {user}")
|
||||||
|
|
||||||
|
else:
|
||||||
|
logging.error(f"unsupported action: {data}")
|
||||||
|
finally:
|
||||||
|
# Unregister user
|
||||||
|
user.unregister()
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
async with websockets.serve(handler, "localhost", 6789):
|
||||||
|
await asyncio.Future() # run forever
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
Loading…
Reference in a new issue