Skip to content

Device Layer

Manages serial communication with the OSECHI detector.


Design Intent

The Device Layer is the only layer that touches hardware. Everything above it operates on DeviceProtocol — an interface, not a concrete class — so the rest of the library works identically with real hardware or a mock.

DeviceProtocol (interface)
├── PortDevice   — real serial hardware
└── MockDevice   — in-memory simulation

DeviceProtocol

DeviceProtocol defines the contract that every device implementation must satisfy:

Method Purpose
send_command(command) Write a text command to the device
receive_response(field_type) Read and parse one JSONL line from the device
query(command) send_command + receive_response in one call
close() Release the connection
__enter__ / __exit__ Context manager protocol

query() is the recommended entry point for request/response interactions. send_command and receive_response are exposed separately for streaming scenarios where you send one command and read many responses.


PortDevice

PortDevice wraps pyserial for low-level serial I/O.

Serial spec: 115200 baud, 8N1, default timeout 0.1 s.

Command format: plain text, newline-terminated ("GET_STATUS\n").

Response format: single-line JSONL ({"type":"response","status":"ok",...}).

Auto-detection

connect() is the recommended factory. When port="auto" (default), it probes ports in priority order:

  1. /dev/cu.usbserial* — macOS USB-serial adapters
  2. /dev/ttyUSB* — Linux USB-serial
  3. /dev/ttyACM* — Arduino-style ACM devices
  4. /dev/ttyS* — built-in serial ports
  5. First available port (fallback)

With multiple devices connected, specify the port explicitly to avoid ambiguity.

Usage

from kazunoko import connect

# Factory function (recommended)
with connect() as device:
    response = device.query("GET_STATUS")

# Explicit port
with connect(port="/dev/ttyUSB0") as device:
    response = device.query("GET_STATUS")

# Direct instantiation
from kazunoko import PortDevice
device = PortDevice(port="/dev/ttyUSB0", baudrate=115200, timeout=0.1)

Echo confirmation

After send_command(), the device echoes back the command before sending the response. PortDevice reads and discards the echo line before returning the actual response. This is an implementation detail callers do not need to handle.


Custom device implementations

Any class that satisfies DeviceProtocol works with Command, Reader, Measure, and the CLI:

class NetworkDevice:
    def send_command(self, command: str) -> bool: ...
    def receive_response(self, field_type: str) -> DeviceResponse: ...
    def query(self, command: str) -> DeviceResponse: ...
    def close(self) -> None: ...
    def __enter__(self): return self
    def __exit__(self, *args): self.close()

device = NetworkDevice(host="192.168.1.10")
cmd = Command(device)  # works unchanged