Skip to content

Commit

Permalink
RSDK-4057 Add Power Sensor Component (#142)
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviamiller authored Aug 1, 2023
1 parent 78bf65c commit b784dc4
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 2 deletions.
4 changes: 2 additions & 2 deletions buf.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ deps:
- remote: buf.build
owner: viamrobotics
repository: api
commit: 9983a44b37434cdea5f0f9ff1eb17456
digest: shake256:15c1db788f3bc46c33a5291dce377af9fdb0e1dc9136ec61db508cc777abd6f6978b1cdd1e503a3662bcee66c051c3e1f04986f0c4bd83da846ca6d89aef5328
commit: 95de10381a3b4bd6a399b6ebae933268
digest: shake256:917b4ea97a702b23f1b6ae795a69dc6eb250b98df077323aa3497ea341a8822b78bb410ca3b16d94e68b4f8b73ad81f24070b91302b84e81ecfccaa5d0d28701
- remote: buf.build
owner: viamrobotics
repository: goutils
Expand Down
5 changes: 5 additions & 0 deletions codesamples/apis.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
"func": "getLinearAcceleration",
"args": []
},
"power_sensor": {
"client": "PowerSensorClient",
"func": "getVoltage",
"args": []
},
"servo": {
"client": "ServoClient",
"func": "getPosition"
Expand Down
101 changes: 101 additions & 0 deletions src/components/power-sensor/client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// @vitest-environment happy-dom

import { afterEach, beforeEach, expect, test, vi } from 'vitest';

import { RobotClient } from '../../robot';
import { PowerSensorServiceClient } from '../../gen/component/powersensor/v1/powersensor_pb_service';
import { PowerSensorClient } from './client';

let sensor: PowerSensorClient;
const testPower = 0.5;
const testVoltage = 1.5;
const testCurrent = 1;
const testIsAc = true;

beforeEach(() => {
RobotClient.prototype.createServiceClient = vi
.fn()
.mockImplementation(() => new PowerSensorServiceClient('mysensor'));

PowerSensorServiceClient.prototype.getVoltage = vi
.fn()
.mockImplementation((_req, _md, cb) => {
cb(null, {
getVolts: () => testVoltage,
getIsAc: () => testIsAc,
});
});
PowerSensorServiceClient.prototype.getCurrent = vi
.fn()
.mockImplementation((_req, _md, cb) => {
cb(null, {
getAmperes: () => testCurrent,
getIsAc: () => testIsAc,
});
});

PowerSensorServiceClient.prototype.getPower = vi
.fn()
.mockImplementation((_req, _md, cb) => {
cb(null, {
getWatts: () => testPower,
});
});

sensor = new PowerSensorClient(new RobotClient('host'), 'test-sensor');
});

afterEach(() => {
vi.clearAllMocks();
});

test('individual readings', async () => {
await expect(sensor.getVoltage()).resolves.toStrictEqual([
testVoltage,
testIsAc,
]);
await expect(sensor.getCurrent()).resolves.toStrictEqual([
testCurrent,
testIsAc,
]);
await expect(sensor.getPower()).resolves.toStrictEqual(testPower);
});

test('get readings', async () => {
await expect(sensor.getReadings()).resolves.toStrictEqual({
voltage: testVoltage,
current: testCurrent,
isAc: testIsAc,
power: testPower,
});
});

test('get readings returns without unimplemented fields', async () => {
const unimplementedError = new Error('Unimplemented');

PowerSensorServiceClient.prototype.getVoltage = vi
.fn()
.mockImplementation((_req, _md, cb) => {
cb(unimplementedError, null);
});

await expect(sensor.getVoltage()).rejects.toStrictEqual(unimplementedError);
await expect(sensor.getReadings()).resolves.toStrictEqual({
current: testCurrent,
isAc: testIsAc,
power: testPower,
});
});

test('get readings fails on other errors', async () => {
const unexpectedError = new Error('Jank!');

PowerSensorServiceClient.prototype.getPower = vi
.fn()
.mockImplementation((_req, _md, cb) => {
cb(unexpectedError, null);
});

await expect(sensor.getPower()).rejects.toStrictEqual(unexpectedError);
await expect(sensor.getReadings()).rejects.toStrictEqual(unexpectedError);
});
113 changes: 113 additions & 0 deletions src/components/power-sensor/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Struct } from 'google-protobuf/google/protobuf/struct_pb';
import type { RobotClient } from '../../robot';
import { PowerSensorServiceClient } from '../../gen/component/powersensor/v1/powersensor_pb_service';
import type { Options, StructType } from '../../types';
import pb from '../../gen/component/powersensor/v1/powersensor_pb';
import { promisify, doCommandFromClient } from '../../utils';
import type { PowerSensor, PowerSensorReadings } from './power-sensor';

/**
* A gRPC-web client for the MovementSensor component.
*
* @group Clients
*/

export class PowerSensorClient implements PowerSensor {
private client: PowerSensorServiceClient;
private readonly name: string;
private readonly options: Options;

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

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

async getVoltage(extra = {}) {
const { powersensorService } = this;
const request = new pb.GetVoltageRequest();
request.setName(this.name);
request.setExtra(Struct.fromJavaScript(extra));

this.options.requestLogger?.(request);

const response = await promisify<
pb.GetVoltageRequest,
pb.GetVoltageResponse
>(powersensorService.getVoltage.bind(powersensorService), request);

return [response.getVolts(), response.getIsAc()] as const;
}

async getCurrent(extra = {}) {
const { powersensorService } = this;
const request = new pb.GetCurrentRequest();
request.setName(this.name);
request.setExtra(Struct.fromJavaScript(extra));

this.options.requestLogger?.(request);

const response = await promisify<
pb.GetCurrentRequest,
pb.GetCurrentResponse
>(powersensorService.getCurrent.bind(powersensorService), request);

return [response.getAmperes(), response.getIsAc()] as const;
}

async getPower(extra = {}) {
const { powersensorService } = this;
const request = new pb.GetPowerRequest();
request.setName(this.name);
request.setExtra(Struct.fromJavaScript(extra));

this.options.requestLogger?.(request);

const response = await promisify<pb.GetPowerRequest, pb.GetPowerResponse>(
powersensorService.getPower.bind(powersensorService),
request
);

return response.getWatts();
}

async getReadings(extra = {}) {
const readings: Record<string, any> = {};
try {
[readings['voltage'], readings['isAc']] = await this.getVoltage(extra);
} catch (error) {
if (!(error as Error).message.includes('Unimplemented')) {
throw error;
}
}
try {
[readings['current'], readings['isAc']] = await this.getCurrent(extra);
} catch (error) {
if (!(error as Error).message.includes('Unimplemented')) {
throw error;
}
}
try {
readings['power'] = await this.getPower(extra);
} catch (error) {
if (!(error as Error).message.includes('Unimplemented')) {
throw error;
}
}
return readings as PowerSensorReadings;
}

async doCommand(command: StructType): Promise<StructType> {
const { powersensorService } = this;
return doCommandFromClient(
powersensorService,
this.name,
command,
this.options
);
}
}
19 changes: 19 additions & 0 deletions src/components/power-sensor/power-sensor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { StructType } from '../../types';
import type { Sensor } from '../sensor';

export type PowerSensorReadings = {
voltage?: number;
current?: number;
isAc?: boolean;
power?: number;
};

/** Represents any sensor that reports voltage, current, and/or power */
export interface PowerSensor extends Sensor {
/** Get Voltage in volts and a boolean that returns true if AC */
getVoltage(extra?: StructType): Promise<readonly [number, boolean]>;
/** Get Current in amps and a boolean that returns true if AC */
getCurrent(extra?: StructType): Promise<readonly [number, boolean]>;
/** Get Power in watts */
getPower(extra?: StructType): Promise<number>;
}
5 changes: 5 additions & 0 deletions src/components/powersensor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type {
PowerSensorReadings,
PowerSensor,
} from './power-sensor/power-sensor';
export { PowerSensorClient } from './power-sensor/client';
16 changes: 16 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,22 @@ export {
MovementSensorClient,
} from './components/movementsensor';

/**
* Raw Protobuf interfaces for a PowerSensor component.
*
* Generated with https://github.com/improbable-eng/grpc-web
*
* @deprecated Use {@link PowerSensorClient} instead.
* @alpha
* @group Raw Protobufs
*/
export { default as powerSensorApi } from './gen/component/powersensor/v1/powersensor_pb';
export {
type PowerSensor,
type PowerSensorReadings,
PowerSensorClient,
} from './components/powersensor';

/**
* Raw Protobuf interfaces generated with
* https://github.com/improbable-eng/grpc-web for a Sensor component.
Expand Down

0 comments on commit b784dc4

Please sign in to comment.