Skip to content

Commit aada6aa

Browse files
authored
RSDK-4434 Update get location wrapper (#160)
1 parent c590886 commit aada6aa

File tree

8 files changed

+122
-10
lines changed

8 files changed

+122
-10
lines changed

buf.lock

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ deps:
99
- remote: buf.build
1010
owner: viamrobotics
1111
repository: api
12-
commit: 567db2ef2dd04c548371f69ab31a1226
13-
digest: shake256:a8bfb0b7ab720092320c8a302e89e65a4de510f8816ee18ae86673d157b70f2d3433c90dffce82ed7be2bc226781e41f3e558d9e89510a2ceccac001f7dc63cb
12+
commit: 82d243f345744a249103db65f136f400
13+
digest: shake256:4c885426d51826e69331f33f55ce02d6764509dc47b919790e36fad9401e2cf593e3a610506b4be144356fed045570e9933a8fcd3f3e704663ebfca5c33e7552
1414
- remote: buf.build
1515
owner: viamrobotics
1616
repository: goutils

src/main.ts

+1
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ export { default as navigationApi } from './gen/service/navigation/v1/navigation
306306
export {
307307
type ModeMap,
308308
type Waypoint,
309+
type NavigationPosition,
309310
NavigationClient,
310311
} from './services/navigation';
311312

src/services/navigation.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export type { Navigation } from './navigation/navigation';
2-
export type { ModeMap, Waypoint } from './navigation/types';
2+
export type { ModeMap, Waypoint, NavigationPosition } from './navigation/types';
33
export { NavigationClient } from './navigation/client';
+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// @vitest-environment happy-dom
2+
3+
import { type Mock, beforeEach, describe, expect, test, vi } from 'vitest';
4+
import { NavigationServiceClient } from '../../gen/service/navigation/v1/navigation_pb_service';
5+
vi.mock('../../gen/service/navigation/v1/navigation_pb_service');
6+
import { RobotClient } from '../../robot';
7+
vi.mock('../../robot');
8+
9+
import { NavigationClient } from './client';
10+
11+
const navigationClientName = 'test-navigation';
12+
13+
let navigation: NavigationClient;
14+
15+
beforeEach(() => {
16+
RobotClient.prototype.createServiceClient = vi
17+
.fn()
18+
.mockImplementation(
19+
() => new NavigationServiceClient(navigationClientName)
20+
);
21+
22+
navigation = new NavigationClient(
23+
new RobotClient('host'),
24+
navigationClientName
25+
);
26+
});
27+
28+
const testLatitude = 50;
29+
const testLongitude = 75;
30+
const testCompassHeading = 90;
31+
32+
describe('getLocation', () => {
33+
let latitude: Mock<[], number>;
34+
let longitude: Mock<[], number>;
35+
let compassHeading: Mock<[], number>;
36+
let location: Mock<[], { latitude: number; longitude: number }>;
37+
38+
beforeEach(() => {
39+
location = vi.fn(() => ({
40+
latitude: latitude(),
41+
longitude: longitude(),
42+
}));
43+
44+
NavigationServiceClient.prototype.getLocation = vi
45+
.fn()
46+
.mockImplementation((_req, _md, cb) => {
47+
cb(null, {
48+
toObject: () => ({
49+
compassHeading: compassHeading(),
50+
location: location(),
51+
}),
52+
});
53+
});
54+
});
55+
56+
test('null location', async () => {
57+
location = vi.fn();
58+
compassHeading = vi.fn();
59+
60+
await expect(navigation.getLocation()).rejects.toThrowError(
61+
/^no location$/u
62+
);
63+
64+
expect(location).toHaveBeenCalledOnce();
65+
expect(compassHeading).toHaveBeenCalledOnce();
66+
});
67+
68+
test('valid geopoint', async () => {
69+
latitude = vi.fn(() => testLatitude);
70+
longitude = vi.fn(() => testLongitude);
71+
compassHeading = vi.fn(() => testCompassHeading);
72+
73+
const expected = {
74+
location: { latitude: testLatitude, longitude: testLongitude },
75+
compassHeading: testCompassHeading,
76+
};
77+
78+
await expect(navigation.getLocation()).resolves.toStrictEqual(expected);
79+
80+
expect(location).toHaveBeenCalledOnce();
81+
expect(compassHeading).toHaveBeenCalledOnce();
82+
});
83+
84+
test('invalid geopoint', async () => {
85+
latitude = vi.fn(() => Number.NaN);
86+
longitude = vi.fn(() => Number.NaN);
87+
88+
await expect(navigation.getLocation()).rejects.toThrowError(
89+
/^invalid location$/u
90+
);
91+
92+
expect(location).toHaveBeenCalledOnce();
93+
expect(compassHeading).toHaveBeenCalledOnce();
94+
});
95+
});

src/services/navigation/client.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { RobotClient } from '../../robot';
44
import { NavigationServiceClient } from '../../gen/service/navigation/v1/navigation_pb_service';
55
import { doCommandFromClient, encodeGeoPoint, promisify } from '../../utils';
66
import type { GeoPoint, Options, StructType } from '../../types';
7+
import { isValidGeoPoint } from '../../types';
78
import type { ModeMap } from './types';
89
import type { Navigation } from './navigation';
910

@@ -74,11 +75,14 @@ export class NavigationClient implements Navigation {
7475
pb.GetLocationResponse
7576
>(service.getLocation.bind(service), request);
7677

77-
const result = response.getLocation();
78-
if (!result) {
78+
const result = response.toObject();
79+
if (!result.location) {
7980
throw new Error('no location');
8081
}
81-
return result.toObject();
82+
if (!isValidGeoPoint(result.location)) {
83+
throw new Error('invalid location');
84+
}
85+
return result;
8286
}
8387

8488
async getWayPoints(extra = {}) {

src/services/navigation/navigation.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { GeoObstacle, GeoPoint, Resource, StructType } from '../../types';
2-
import type { ModeMap, Waypoint } from './types';
2+
import type { ModeMap, Waypoint, NavigationPosition } from './types';
33

44
/**
55
* A service that uses GPS to automatically navigate a robot to user defined
@@ -17,10 +17,10 @@ export interface Navigation extends Resource {
1717
setMode: (mode: ModeMap[keyof ModeMap], extra?: StructType) => Promise<void>;
1818

1919
/** Get the current location of the robot. */
20-
getLocation: (extra?: StructType) => Promise<GeoPoint>;
20+
getLocation: (extra?: StructType) => Promise<NavigationPosition>;
2121

2222
/** Get an array of waypoints currently in the service's data storage. */
23-
getWayPoints: (extra?: StructType) => Promise<Array<Waypoint>>;
23+
getWayPoints: (extra?: StructType) => Promise<Waypoint[]>;
2424

2525
/**
2626
* Add a waypoint to the service's data storage.
@@ -39,5 +39,5 @@ export interface Navigation extends Resource {
3939
removeWayPoint: (id: string, extra?: StructType) => Promise<void>;
4040

4141
/** Get a list of obstacles. */
42-
getObstacles: (extra?: StructType) => Promise<Array<GeoObstacle>>;
42+
getObstacles: (extra?: StructType) => Promise<GeoObstacle[]>;
4343
}

src/services/navigation/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ import pb from '../../gen/service/navigation/v1/navigation_pb';
22

33
export type ModeMap = pb.ModeMap;
44
export type Waypoint = pb.Waypoint.AsObject;
5+
export type NavigationPosition = pb.GetLocationResponse.AsObject;

src/types.ts

+11
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ export type ResourceName = common.ResourceName.AsObject;
2323
export type GeoObstacle = common.GeoObstacle.AsObject;
2424
export type GeoPoint = common.GeoPoint.AsObject;
2525

26+
export const isValidGeoPoint = (value: GeoPoint) => {
27+
const { latitude, longitude } = value;
28+
29+
return !(
30+
typeof latitude !== 'number' ||
31+
typeof longitude !== 'number' ||
32+
Number.isNaN(latitude) ||
33+
Number.isNaN(longitude)
34+
);
35+
};
36+
2637
// Spatial Math
2738
export type Vector3 = common.Vector3.AsObject;
2839
export type Orientation = common.Orientation.AsObject;

0 commit comments

Comments
 (0)