133 lines
3.8 KiB
Python
133 lines
3.8 KiB
Python
import json
|
|
import random
|
|
import socket
|
|
import struct
|
|
import threading
|
|
import time
|
|
from typing import Any, Dict, Tuple
|
|
|
|
HOST = "192.168.8.2"
|
|
PORT = 8888
|
|
CLIENT_DEVICE_ID = 0
|
|
MIN_TEST_VLC = 5.0
|
|
MAX_TEST_VLC = 6.0
|
|
SEND_INTERVAL_SECONDS = 1.0
|
|
RECV_TIMEOUT_SECONDS = 1.0
|
|
CONNECT_TIMEOUT_SECONDS = 5.0
|
|
MAX_PAYLOAD_BYTES = 64 * 1024
|
|
HEADER_STRUCT = struct.Struct("<II")
|
|
|
|
|
|
def encode_packet(payload: Dict[str, Any], device_id: int) -> bytes:
|
|
"""Encode a JSON payload using the protocol binary header."""
|
|
payload_bytes = json.dumps(
|
|
payload,
|
|
ensure_ascii=True,
|
|
separators=(",", ":"),
|
|
).encode("utf-8")
|
|
return HEADER_STRUCT.pack(device_id, len(payload_bytes)) + payload_bytes
|
|
|
|
|
|
def recv_exactly(sock: socket.socket, size: int) -> bytes:
|
|
"""Receive an exact number of bytes from the socket."""
|
|
chunks = bytearray()
|
|
|
|
while len(chunks) < size:
|
|
chunk = sock.recv(size - len(chunks))
|
|
if not chunk:
|
|
raise ConnectionError("Connection closed by peer.")
|
|
chunks.extend(chunk)
|
|
|
|
return bytes(chunks)
|
|
|
|
|
|
def read_packet(sock: socket.socket) -> Tuple[int, Any]:
|
|
"""Read and decode a single packet from the server."""
|
|
header_bytes = recv_exactly(sock, HEADER_STRUCT.size)
|
|
device_id, payload_length = HEADER_STRUCT.unpack(header_bytes)
|
|
|
|
if payload_length > MAX_PAYLOAD_BYTES:
|
|
raise ValueError(
|
|
"Payload length %d exceeds the %d byte limit."
|
|
% (payload_length, MAX_PAYLOAD_BYTES)
|
|
)
|
|
|
|
payload_bytes = recv_exactly(sock, payload_length)
|
|
payload = json.loads(payload_bytes.decode("utf-8"))
|
|
return device_id, payload
|
|
|
|
def send_vlc_message(sock: socket.socket, device_id: int, vlc: float) -> None:
|
|
"""Send one test client payload to the server."""
|
|
payload = {"vlc": vlc}
|
|
sock.sendall(encode_packet(payload, device_id=device_id))
|
|
print(f'>>> sent {json.dumps(payload, ensure_ascii=False)} device_id={device_id}')
|
|
|
|
|
|
def receive_loop(sock: socket.socket, stop_event: threading.Event) -> None:
|
|
"""Print all packets received from the locator server."""
|
|
while not stop_event.is_set():
|
|
try:
|
|
device_id, payload = read_packet(sock)
|
|
except socket.timeout:
|
|
continue
|
|
except (ConnectionError, OSError, ValueError, json.JSONDecodeError) as error:
|
|
if not stop_event.is_set():
|
|
print("Receiver stopped:", error)
|
|
stop_event.set()
|
|
return
|
|
|
|
print(f"<<< received device_id={device_id}")
|
|
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
|
|
|
|
def send_loop(sock: socket.socket, stop_event: threading.Event) -> None:
|
|
"""Send test vlc packets until the client is stopped."""
|
|
send_vlc_message(
|
|
sock,
|
|
CLIENT_DEVICE_ID,
|
|
round(random.uniform(MIN_TEST_VLC, MAX_TEST_VLC), 2),
|
|
)
|
|
|
|
if SEND_INTERVAL_SECONDS <= 0:
|
|
while not stop_event.is_set():
|
|
time.sleep(0.1)
|
|
return
|
|
|
|
while not stop_event.is_set():
|
|
time.sleep(SEND_INTERVAL_SECONDS)
|
|
send_vlc_message(
|
|
sock,
|
|
CLIENT_DEVICE_ID,
|
|
round(random.uniform(MIN_TEST_VLC, MAX_TEST_VLC), 2),
|
|
)
|
|
|
|
|
|
def main() -> None:
|
|
"""Run the client until interrupted or disconnected."""
|
|
stop_event = threading.Event()
|
|
|
|
with socket.create_connection(
|
|
(HOST, PORT),
|
|
timeout=CONNECT_TIMEOUT_SECONDS,
|
|
) as sock:
|
|
sock.settimeout(RECV_TIMEOUT_SECONDS)
|
|
print(f"Connected to {HOST}:{PORT}")
|
|
|
|
receiver = threading.Thread(
|
|
target=receive_loop,
|
|
args=(sock, stop_event),
|
|
daemon=True,
|
|
)
|
|
receiver.start()
|
|
|
|
try:
|
|
send_loop(sock, stop_event)
|
|
except KeyboardInterrupt:
|
|
print("Stopping client.")
|
|
finally:
|
|
stop_event.set()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|