added remote k209 setup
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
"""Binary TCP protocol shared by the K209 remote server and Python client."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import socket
|
||||
import struct
|
||||
from typing import BinaryIO
|
||||
|
||||
import numpy as np
|
||||
|
||||
COMMAND_IDENTITY = b"I"
|
||||
COMMAND_LIMITS = b"L"
|
||||
COMMAND_CONFIGURE = b"C"
|
||||
COMMAND_ACQUIRE = b"A"
|
||||
|
||||
STATUS_OK = b"O"
|
||||
STATUS_ERROR = b"E"
|
||||
|
||||
DEFAULT_REMOTE_HOST = "127.0.0.1"
|
||||
DEFAULT_REMOTE_PORT = 50209
|
||||
|
||||
CONFIG_STRUCT = struct.Struct("!ddIdd")
|
||||
LIMITS_STRUCT = struct.Struct("!ddddIdd")
|
||||
U32_STRUCT = struct.Struct("!I")
|
||||
|
||||
|
||||
def recv_exact(stream: socket.socket | BinaryIO, size: int) -> bytes:
|
||||
"""Read exactly `size` bytes from a socket-like object."""
|
||||
chunks: list[bytes] = []
|
||||
remaining = int(size)
|
||||
while remaining > 0:
|
||||
chunk = stream.recv(remaining) if isinstance(stream, socket.socket) else stream.read(remaining)
|
||||
if not chunk:
|
||||
raise ConnectionError(f"K209 remote connection closed with {remaining} bytes pending")
|
||||
chunks.append(chunk)
|
||||
remaining -= len(chunk)
|
||||
return b"".join(chunks)
|
||||
|
||||
|
||||
def send_all(stream: socket.socket | BinaryIO, payload: bytes) -> None:
|
||||
"""Write all payload bytes to a socket-like object."""
|
||||
if isinstance(stream, socket.socket):
|
||||
stream.sendall(payload)
|
||||
return
|
||||
stream.write(payload)
|
||||
|
||||
|
||||
def send_u32(stream: socket.socket | BinaryIO, value: int) -> None:
|
||||
"""Send one network-order uint32."""
|
||||
send_all(stream, U32_STRUCT.pack(int(value)))
|
||||
|
||||
|
||||
def recv_u32(stream: socket.socket | BinaryIO) -> int:
|
||||
"""Read one network-order uint32."""
|
||||
return int(U32_STRUCT.unpack(recv_exact(stream, U32_STRUCT.size))[0])
|
||||
|
||||
|
||||
def send_error(stream: socket.socket | BinaryIO, message: str) -> None:
|
||||
"""Send protocol error response."""
|
||||
payload = str(message).encode("utf-8", errors="replace")
|
||||
send_all(stream, STATUS_ERROR)
|
||||
send_u32(stream, len(payload))
|
||||
send_all(stream, payload)
|
||||
|
||||
|
||||
def read_status(stream: socket.socket | BinaryIO) -> None:
|
||||
"""Read response status and raise remote error when needed."""
|
||||
status = recv_exact(stream, 1)
|
||||
if status == STATUS_OK:
|
||||
return
|
||||
if status == STATUS_ERROR:
|
||||
message = recv_exact(stream, recv_u32(stream)).decode("utf-8", errors="replace")
|
||||
raise RuntimeError(f"K209 remote server error: {message}")
|
||||
raise RuntimeError(f"K209 remote server returned invalid status byte: {status!r}")
|
||||
|
||||
|
||||
def send_float32_array(stream: socket.socket | BinaryIO, values: np.ndarray) -> None:
|
||||
"""Send a float32 array as a length-prefixed little-endian payload."""
|
||||
payload = np.asarray(values, dtype="<f4").tobytes(order="C")
|
||||
send_u32(stream, len(payload))
|
||||
send_all(stream, payload)
|
||||
|
||||
|
||||
def recv_float32_array(stream: socket.socket | BinaryIO, expected_values: int) -> np.ndarray:
|
||||
"""Read a length-prefixed little-endian float32 array."""
|
||||
payload_size = recv_u32(stream)
|
||||
expected_size = int(expected_values) * np.dtype(np.float32).itemsize
|
||||
if payload_size != expected_size:
|
||||
raise RuntimeError(f"K209 remote payload has {payload_size} bytes, expected {expected_size}")
|
||||
return np.frombuffer(recv_exact(stream, payload_size), dtype="<f4").copy()
|
||||
Reference in New Issue
Block a user