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:
/dev/cu.usbserial*— macOS USB-serial adapters/dev/ttyUSB*— Linux USB-serial/dev/ttyACM*— Arduino-style ACM devices/dev/ttyS*— built-in serial ports- 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