gRPC development guide / gRPC Quick Start / Event subscriptions
The gRPC Frame exposes ordinary DeviceClass methods through Describe / Call on ctrl_obj. Continuous results use the separate StreamCall endpoint on stream_obj. Streaming is optional: pass stream_obj only when required. A public streaming method returns an iterator. Each yielded value is serialized by adapter.pack_result() and reconstructed on the client by adapter.unpack_result().
DeviceClass ─ ctrl_obj ─ Describe / Call ─ client methods
└─ stream_obj ─ StreamCall ─ client.stream_call(...)
Streaming methods are not automatically bound by Describe. Call stream_call(method_name, *args, **kwargs) explicitly.
The MiniPIX implementation registers an independent streaming adapter. _MiniPixStream is defined in the MiniPIX package, not in the Frame.
from grpc_frame.grpc_server import create_grpc_server
ctrl = MiniPixCtrl(logger=logger, log_level=log_level)
ctrl.connect()
stream_obj = _MiniPixStream(ctrl)
server = create_grpc_server(
ctrl_obj=ctrl,
stream_obj=stream_obj,
logger=logger,
log_level=log_level,
max_workers=10,
)
_MiniPixStream.acquire_frames(count, sec) and acquire_integral(count, sec) yield from a common _acquire() implementation. Its measurement callback places frames in a queue.Queue; a worker thread performs acquisition while the generator yields queued frames. Queue messages also signal completion and errors. This keeps gRPC streaming outside the DeviceClass.
from grpc_frame.grpc_client import GrpcClient
client = GrpcClient(server_ip="127.0.0.1", server_port=50051)
try:
for frame in client.stream_call("acquire_frames", 3, 0.1):
print(type(frame))
finally:
client.close()
Adjust the address and measurement parameters to the device server. stream_call() returns a generator. The client implementation cancels the underlying RPC when the iterator is closed; the server checks context.is_active() and closes the source iterator when streaming ends.
MiniPIX also demonstrates a different path, ordinary Call returning acquisition data that is saved locally by specialized clients. AsyncPixetGrpcClient stores dynamically bound remote methods as _grpc_acquire_frames and _grpc_acquire_integral, then replaces the public methods with local-save wrappers. SyncPixetGrpcClient follows the same pattern.
# Extract from AsyncPixetGrpcClient
result = await self._grpc_acquire_frames(
count, sec, save_format=save_format, save_path=save_path,
)
return self._finish_acquisition(result, name, save_local, return_data)
This is not a StreamCall example: the remote acquisition method uses ordinary RPC. TIFF output preserves numerical arrays; PNG output converts data to display-oriented 16-bit grayscale. Server and client grpc_options can be used to configure gRPC message-size limits for large images. Consider frame sizes, consumer throughput, queue growth, and cleanup on disconnection.
mini_pix/src/mini_pix/server_grpc/pixet_grpc_server.py: streaming adapter and server registration.mini_pix/src/mini_pix/server_grpc/async_pixet_grpc_client.py: client-side acquisition overrides and file saving.mini_pix/src/mini_pix/server_grpc/sync_pixet_grpc_client.py: synchronous counterpart.grpc_frame/grpc_server.py and grpc_frame/grpc_client.py: StreamCall implementation.