-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
RSDK-4057 Add Power Sensor Component (#142)
- Loading branch information
1 parent
78bf65c
commit b784dc4
Showing
7 changed files
with
261 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters