vita_gamepad/vitapad/receiver.py

107 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import socket
import threading
import time
from vitapad.backends import GamepadBackend
from vitapad.protocol import (
DISCOVERY_MAGIC,
InputState,
decode_packet,
sequence_is_newer,
)
class Receiver:
def __init__(
self,
backend: GamepadBackend,
bind: str = "0.0.0.0",
port: int = 5000,
discovery_port: int = 5001,
allow: str | None = None,
timeout_ms: int = 300,
) -> None:
self.backend = backend
self.bind = bind
self.port = port
self.discovery_port = discovery_port
self.allow = allow
self.timeout = timeout_ms / 1000.0
self._stop = threading.Event()
self._last_packet = 0.0
self._connected_address: str | None = None
self._last_sequence: int | None = None
self._neutral_sent = True
def _broadcast_discovery(self) -> None:
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as beacon:
beacon.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
while not self._stop.wait(1.0):
try:
beacon.sendto(
DISCOVERY_MAGIC, ("255.255.255.255", self.discovery_port)
)
except OSError:
# Interfaces can disappear during sleep/network changes.
pass
def run(self) -> None:
discovery = threading.Thread(
target=self._broadcast_discovery, name="discovery", daemon=True
)
discovery.start()
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as listener:
listener.bind((self.bind, self.port))
listener.settimeout(0.05)
print(
f"正在监听 {self.bind}:{self.port},后端: {self.backend.name}\n"
"等待 PSVitaCtrl+C 退出)..."
)
try:
while not self._stop.is_set():
self._receive_once(listener)
self._apply_failsafe()
finally:
self._stop.set()
discovery.join(timeout=1.2)
self.backend.close()
def _receive_once(self, listener: socket.socket) -> None:
try:
data, address = listener.recvfrom(256)
except socket.timeout:
return
if self.allow and address[0] != self.allow:
return
try:
state = decode_packet(data)
except ValueError:
return
if self._connected_address != address[0]:
self._connected_address = address[0]
self._last_sequence = None
print(f"已连接 PSVita: {address[0]}")
if self._last_sequence is not None and not sequence_is_newer(
state.sequence, self._last_sequence
):
return
self._last_sequence = state.sequence
self._last_packet = time.monotonic()
self._neutral_sent = False
self.backend.update(state)
def _apply_failsafe(self) -> None:
if (
not self._neutral_sent
and time.monotonic() - self._last_packet >= self.timeout
):
self.backend.update(InputState.neutral())
self._neutral_sent = True
print("连接超时,已释放全部按键;等待重连...")
def stop(self) -> None:
self._stop.set()