Skip to content

Commit

Permalink
APP-4942: Add input controller client (#318)
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanlookpotts authored Jul 1, 2024
1 parent af27db7 commit 8f7db2c
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 19 deletions.
75 changes: 75 additions & 0 deletions src/components/input-controller/client.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// @vitest-environment happy-dom

import { beforeEach, describe, expect, it, vi } from 'vitest';
import {
Event as EventPb,
TriggerEventRequest,
} from '../../gen/component/inputcontroller/v1/input_controller_pb';
import { InputControllerServiceClient } from '../../gen/component/inputcontroller/v1/input_controller_pb_service';
import { RobotClient } from '../../robot';
import { InputControllerClient } from './client';
import type { InputControllerEvent } from './input-controller';
vi.mock('../../robot');
vi.mock('../../gen/service/input_controller/v1/input_controller_pb_service');

const inputControllerClientName = 'test-input-controller';

let inputController: InputControllerClient;

const event: InputControllerEvent = {
event: 'some-event',
value: 0.5,
time: undefined,
control: 'some-control',
};
const eventPb = (() => {
const pb = new EventPb();
pb.setEvent(event.event);
pb.setValue(event.value);
pb.setControl(event.control);
return pb;
})();

describe('InputControllerClient Tests', () => {
beforeEach(() => {
RobotClient.prototype.createServiceClient = vi
.fn()
.mockImplementation(
() => new InputControllerServiceClient(inputControllerClientName)
);

InputControllerServiceClient.prototype.getEvents = vi
.fn()
.mockImplementation((_req, _md, cb) => {
cb(null, {
getEventsList: () => [eventPb],
});
});

InputControllerServiceClient.prototype.triggerEvent = vi
.fn()
.mockImplementation((req: TriggerEventRequest, _md, cb) => {
expect(req.getEvent()?.getEvent()).toStrictEqual(event.event);
expect(req.getEvent()?.getValue()).toStrictEqual(event.value);
expect(req.getEvent()?.getControl()).toStrictEqual(event.control);
cb(null, {
triggerEvent: vi.fn(),
});
});

inputController = new InputControllerClient(
new RobotClient('host'),
inputControllerClientName
);
});

it('gets events', async () => {
const expected = [event];

await expect(inputController.getEvents()).resolves.toStrictEqual(expected);
});

it('triggers events', async () => {
await expect(inputController.triggerEvent(event)).resolves.not.toThrow();
});
});
93 changes: 93 additions & 0 deletions src/components/input-controller/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Struct } from 'google-protobuf/google/protobuf/struct_pb';

import type { RobotClient } from '../../robot';
import type { Options, StructType } from '../../types';
import { InputControllerServiceClient } from '../../gen/component/inputcontroller/v1/input_controller_pb_service';

import { promisify, doCommandFromClient } from '../../utils';
import {
GetEventsRequest,
GetEventsResponse,
TriggerEventRequest,
TriggerEventResponse,
Event,
} from '../../gen/component/inputcontroller/v1/input_controller_pb';
import type { InputController, InputControllerEvent } from './input-controller';
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';

/**
* A gRPC-web client for the Input Controller component.
*
* @group Clients
*/
export class InputControllerClient implements InputController {
private client: InputControllerServiceClient;
private readonly name: string;
private readonly options: Options;

constructor(client: RobotClient, name: string, options: Options = {}) {
this.client = client.createServiceClient(InputControllerServiceClient);
this.name = name;
this.options = options;
}

private get inputControllerService() {
return this.client;
}

async getEvents(extra = {}) {
const { inputControllerService } = this;
const request = new GetEventsRequest();
request.setController(this.name);
request.setExtra(Struct.fromJavaScript(extra));

this.options.requestLogger?.(request);

const response = await promisify<GetEventsRequest, GetEventsResponse>(
inputControllerService.getEvents.bind(inputControllerService),
request
);

return response.getEventsList().map((event) => event.toObject());
}

async triggerEvent(
{ event, time, control, value }: InputControllerEvent,
extra = {}
): Promise<void> {
const { inputControllerService } = this;
const request = new TriggerEventRequest();
request.setController(this.name);

const eventPb = new Event();
eventPb.setEvent(event);
eventPb.setControl(control);
eventPb.setValue(value);
if (time) {
const timePb = new Timestamp();
timePb.setSeconds(time.seconds);
timePb.setNanos(time.nanos);
eventPb.setTime(timePb);
}

request.setEvent(eventPb);
request.setExtra(Struct.fromJavaScript(extra));

this.options.requestLogger?.(request);

await promisify<TriggerEventRequest, TriggerEventResponse>(
inputControllerService.triggerEvent.bind(inputControllerService),
request
);
}

async doCommand(command: StructType): Promise<StructType> {
const { inputControllerService } = this;
return doCommandFromClient(
inputControllerService,
this.name,
command,
this.options
);
}
}
20 changes: 20 additions & 0 deletions src/components/input-controller/input-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Resource, StructType } from '../../types';
import pb from '../../gen/component/inputcontroller/v1/input_controller_pb';

export type InputControllerEvent = pb.Event.AsObject;

/**
* Represents a human interface device like a mouse or keyboard that emits
* events for controls.
*/
export interface InputController extends Resource {
/** Returns a list of events representing the last event on each control. */
getEvents(extra?: StructType): Promise<InputControllerEvent[]>;

/**
* TriggerEvent, where supported, injects an InputControllerEvent into an
* input controller to (virtually) generate events like button presses or axis
* movements
*/
triggerEvent(event: InputControllerEvent, extra?: StructType): Promise<void>;
}
5 changes: 5 additions & 0 deletions src/components/inputcontroller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type {
InputController,
InputControllerEvent,
} from './input-controller/input-controller';
export { InputControllerClient } from './input-controller/client';
21 changes: 2 additions & 19 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,29 +321,12 @@ export { type Gripper, GripperClient } from './components/gripper';
*
* Generated with https://github.com/improbable-eng/grpc-web
*
* @example
*
* ```ts
* import { grpc } from '@improbable-eng/grpc-web';
*
* const client = {}; // replace with a connected robot client
*
* const request = new inputControllerApi.GetControlsRequest();
* request.setController('myinputcontroller');
*
* client.inputControllerService.getControls(
* request,
* new grpc.Metadata(),
* (error, response) => {
* // do something with error or response
* }
* );
* ```
*
* @deprecated Use {@link GripperClient} instead.
* @alpha
* @group Raw Protobufs
*/
export { default as inputControllerApi } from './gen/component/inputcontroller/v1/input_controller_pb';
export * from './components/inputcontroller';

/**
* Raw Protobuf interfaces for a Motion service.
Expand Down

0 comments on commit 8f7db2c

Please sign in to comment.