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(" 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()