Java client#

pw_rpc: Efficient, low-code-size RPC system for embedded devices

pw_rpc provides a Java / Kotlin client implementation under the package dev.pigweed.pw_rpc for Android applications, desktop JVM tools, and automated test frameworks.

Quick start#

The Java client interacts with an RPC server through Channels, Services, and MethodClients.

1. Create Channels and Client#

Define a channel with a send callback (e.g. over USB, BLE, or TCP sockets), and instantiate the dev.pigweed.pw_rpc.Client:

  public static Client createClient(Service sensorService) {
    // 1. Define channel output
    Channel channel = new Channel(1, (byte[] data) -> {
      // Transmit byte buffer over physical transport (USB, Bluetooth, etc.)
      System.out.println("Sending " + data.length + " bytes to device");
    });

    // 2. Instantiate Client
    return Client.createMultiCall(List.of(channel), List.of(sensorService));
  }

2. Route Incoming Packets#

When your transport receives incoming packets from the device, pass them into client.processPacket():

  public static void onDataReceived(Client client, byte[] rawPacket) {
    client.processPacket(ByteBuffer.wrap(rawPacket));
  }

3. Invoke RPC Methods#

Unary RPC#

Invoke a unary method using a dev.pigweed.pw_rpc.StreamObserver:

  public static <TReq, TResp> Call callUnary(
      Client client, MethodClient methodClient, TReq request) {
    return methodClient.invokeUnary(request, new StreamObserver<TResp>() {
      @Override
      public void onNext(TResp response) {
        System.out.println("Received response: " + response);
      }

      @Override
      public void onCompleted(Status status) {
        System.out.println("RPC finished with status: " + status);
      }

      @Override
      public void onError(Status status) {
        System.err.println("RPC failed with error: " + status);
      }
    });
  }

Server Streaming RPC#

A server streaming RPC invokes the onNext callback for each streamed response packet until the stream completes:

  public static <TReq, TResp> Call callServerStreaming(
      Client client, MethodClient methodClient, TReq request) {
    return methodClient.invokeServerStreaming(request, new StreamObserver<TResp>() {
      @Override
      public void onNext(TResp response) {
        System.out.println("Stream response: " + response);
      }

      @Override
      public void onCompleted(Status status) {
        System.out.println("Stream completed: " + status);
      }

      @Override
      public void onError(Status status) {
        System.err.println("Stream error: " + status);
      }
    });
  }

Client & Bidirectional Streaming RPCs#

For client and bidirectional streaming calls, the returned call object allows streaming request messages:

  public static <TReq, TResp> void callBidirectionalStreaming(
      Client client, MethodClient methodClient, TReq chunk1, TReq chunk2) {
    StreamObserver<TResp> observer = new StreamObserver<>() {
      @Override
      public void onNext(TResp response) {
        System.out.println("Stream response: " + response);
      }

      @Override
      public void onCompleted(Status status) {
        System.out.println("Stream finished: " + status);
      }

      @Override
      public void onError(Status status) {
        System.err.println("Stream error: " + status);
      }
    };

    Call.ClientStreaming<TReq> stream = methodClient.invokeBidirectionalStreaming(observer);

    // Stream requests to server
    stream.send(chunk1);
    stream.send(chunk2);

    // Finish sending from client
    stream.finish();
  }

Future-based invocation#

pw_rpc also supports ListenableFuture wrappers for asynchronous Java code:

  public static <TReq, TResp> void callUnaryFuture(
      Client client, MethodClient methodClient, TReq request) {
    ListenableFuture<UnaryResult<TResp>> future = methodClient.invokeUnaryFuture(request);

    // Access result or attach listeners:
    Futures.addCallback(future, new FutureCallback<UnaryResult<TResp>>() {
      @Override
      public void onSuccess(UnaryResult<TResp> result) {
        if (result.status().ok()) {
          System.out.println("Result: " + result.response());
        }
      }

      @Override
      public void onFailure(Throwable t) {
        t.printStackTrace();
      }
    }, MoreExecutors.directExecutor());
  }