diff --git a/src/services/data-manager/client.ts b/src/services/data-manager/client.ts index 5e05b8c60..320ae8ab5 100644 --- a/src/services/data-manager/client.ts +++ b/src/services/data-manager/client.ts @@ -1,14 +1,18 @@ import { Struct, type JsonValue } from '@bufbuild/protobuf'; -import type { CallOptions, PromiseClient } from '@connectrpc/connect'; +import type { CallOptions, Client } from '@connectrpc/connect'; +import { MimeType } from '../../gen/app/datasync/v1/data_sync_pb.js'; import { DataManagerService } from '../../gen/service/datamanager/v1/data_manager_connect.js'; -import { SyncRequest } from '../../gen/service/datamanager/v1/data_manager_pb.js'; +import { + SyncRequest, + UploadBinaryDataToDatasetsRequest, +} from '../../gen/service/datamanager/v1/data_manager_pb.js'; import type { RobotClient } from '../../robot'; import type { Options } from '../../types'; import { doCommandFromClient } from '../../utils'; import type { DataManager } from './data-manager'; export class DataManagerClient implements DataManager { - private client: PromiseClient; + private client: Client; public readonly name: string; private readonly options: Options; public callOptions: CallOptions = { headers: {} as Record }; @@ -80,4 +84,49 @@ export class DataManagerClient implements DataManager { callOptions ); } + + /** + * Uploads binary data to specified datasets. + * + * @example + * + * ```ts + * const dataManager = new VIAM.DataManagerClient( + * machine, + * 'my_data_manager' + * ); + * await dataManager.uploadBinaryDataToDatasets( + * new Uint8Array([1, 2, 3]), + * ['tag1', 'tag2'], + * ['datasetId1', 'datasetId2'], + * MimeType.MIME_TYPE_JPEG + * ); + * ``` + * + * @param binaryData - The binary data to upload. + * @param tags - Tags to associate with the binary data. + * @param datasetIds - IDs of the datasets to associate the binary data with. + * @param mimeType - The MIME type of the binary data. + * @param extra - Extra arguments to pass to the upload request. + * @param callOptions - Call options for the upload request. + */ + async uploadBinaryDataToDatasets( + binaryData: Uint8Array, + tags: string[], + datasetIds: string[], + mimeType: MimeType, + extra = {}, + callOptions = this.callOptions + ) { + const request = new UploadBinaryDataToDatasetsRequest({ + name: this.name, + binaryData, + tags, + datasetIds, + mimeType, + extra: Struct.fromJson(extra), + }); + this.options.requestLogger?.(request); + await this.client.uploadBinaryDataToDatasets(request, callOptions); + } } diff --git a/src/services/data-manager/data-manager.ts b/src/services/data-manager/data-manager.ts index 41ab8828f..1d7942e70 100644 --- a/src/services/data-manager/data-manager.ts +++ b/src/services/data-manager/data-manager.ts @@ -1,6 +1,14 @@ import type { Struct } from '@bufbuild/protobuf'; +import { MimeType } from '../../gen/app/datasync/v1/data_sync_pb.js'; import type { Resource } from '../../types'; export interface DataManager extends Resource { sync: (extra?: Struct) => Promise; + uploadBinaryDataToDatasets: ( + binaryData: Uint8Array, + tags: string[], + datasetIds: string[], + mimeType: MimeType, + extra?: Struct + ) => Promise; } diff --git a/src/services/motion/client.spec.ts b/src/services/motion/client.spec.ts index 6c7e7190f..1bd7d7517 100644 --- a/src/services/motion/client.spec.ts +++ b/src/services/motion/client.spec.ts @@ -8,26 +8,32 @@ vi.mock('../../gen/service/motion/v1/motion_pb_service'); vi.mock('../../robot'); import { Struct, Timestamp } from '@bufbuild/protobuf'; -import { - createPromiseClient, - createRouterTransport, -} from '@connectrpc/connect'; +import { createClient, createRouterTransport } from '@connectrpc/connect'; import { MotionService } from '../../gen/service/motion/v1/motion_connect'; import { GetPlanRequest, ListPlanStatusesRequest, MoveOnGlobeRequest, MoveOnGlobeResponse, + MoveRequest, StopPlanRequest, } from '../../gen/service/motion/v1/motion_pb'; -import { GeoGeometry, GeoPoint, ResourceName } from '../../types'; +import { + GeoGeometry, + GeoPoint, + Pose, + PoseInFrame, + ResourceName, +} from '../../types'; import { MotionClient } from './client'; import { + Constraints, GetPlanResponse, ListPlanStatusesResponse, MotionConfiguration, ObstacleDetector, PlanState, + PseudolinearConstraint, } from './types'; const motionClientName = 'test-motion'; @@ -76,9 +82,7 @@ describe('moveOnGlobe', () => { RobotClient.prototype.createServiceClient = vi .fn() - .mockImplementation(() => - createPromiseClient(MotionService, mockTransport) - ); + .mockImplementation(() => createClient(MotionService, mockTransport)); motion = new MotionClient(new RobotClient('host'), motionClientName); @@ -226,9 +230,7 @@ describe('moveOnGlobe', () => { RobotClient.prototype.createServiceClient = vi .fn() - .mockImplementation(() => - createPromiseClient(MotionService, mockTransport) - ); + .mockImplementation(() => createClient(MotionService, mockTransport)); motion = new MotionClient(new RobotClient('host'), motionClientName); @@ -263,6 +265,64 @@ describe('moveOnGlobe', () => { }); }); +describe('move', () => { + it('sends a move request with pseudolinear constraints', async () => { + const expectedComponentName = new ResourceName({ + namespace: 'viam', + type: 'component', + subtype: 'base', + name: 'myBase', + }); + const expectedDestination = new PoseInFrame({ + referenceFrame: 'world', + pose: new Pose({ + x: 1, + y: 2, + z: 3, + oX: 0, + oY: 0, + oZ: 1, + theta: 90, + }), + }); + const expectedPseudolinearConstraint = new PseudolinearConstraint({ + lineToleranceFactor: 5, + orientationToleranceFactor: 10, + }); + const expectedConstraints = new Constraints({ + pseudolinearConstraint: [expectedPseudolinearConstraint], + }); + const expectedExtra = { some: 'extra' }; + let capturedReq: MoveRequest | undefined; + const mockTransport = createRouterTransport(({ service }) => { + service(MotionService, { + move: (req) => { + capturedReq = req; + return { success: true }; + }, + }); + }); + RobotClient.prototype.createServiceClient = vi + .fn() + .mockImplementation(() => createClient(MotionService, mockTransport)); + motion = new MotionClient(new RobotClient('host'), motionClientName); + await expect( + motion.move( + expectedDestination, + expectedComponentName, + undefined, + expectedConstraints, + expectedExtra + ) + ).resolves.toStrictEqual(true); + expect(capturedReq?.name).toStrictEqual(motionClientName); + expect(capturedReq?.destination).toStrictEqual(expectedDestination); + expect(capturedReq?.componentName).toStrictEqual(expectedComponentName); + expect(capturedReq?.constraints).toStrictEqual(expectedConstraints); + expect(capturedReq?.extra).toStrictEqual(Struct.fromJson(expectedExtra)); + }); +}); + describe('stopPlan', () => { it('return null', async () => { const expectedComponentName = new ResourceName({ @@ -285,9 +345,7 @@ describe('stopPlan', () => { RobotClient.prototype.createServiceClient = vi .fn() - .mockImplementation(() => - createPromiseClient(MotionService, mockTransport) - ); + .mockImplementation(() => createClient(MotionService, mockTransport)); motion = new MotionClient(new RobotClient('host'), motionClientName); @@ -318,9 +376,7 @@ describe('stopPlan', () => { RobotClient.prototype.createServiceClient = vi .fn() - .mockImplementation(() => - createPromiseClient(MotionService, mockTransport) - ); + .mockImplementation(() => createClient(MotionService, mockTransport)); motion = new MotionClient(new RobotClient('host'), motionClientName); @@ -393,9 +449,7 @@ describe('getPlan', () => { RobotClient.prototype.createServiceClient = vi .fn() - .mockImplementation(() => - createPromiseClient(MotionService, mockTransport) - ); + .mockImplementation(() => createClient(MotionService, mockTransport)); motion = new MotionClient(new RobotClient('host'), motionClientName); @@ -430,9 +484,7 @@ describe('getPlan', () => { RobotClient.prototype.createServiceClient = vi .fn() - .mockImplementation(() => - createPromiseClient(MotionService, mockTransport) - ); + .mockImplementation(() => createClient(MotionService, mockTransport)); motion = new MotionClient(new RobotClient('host'), motionClientName); @@ -485,9 +537,7 @@ describe('listPlanStatuses', () => { RobotClient.prototype.createServiceClient = vi .fn() - .mockImplementation(() => - createPromiseClient(MotionService, mockTransport) - ); + .mockImplementation(() => createClient(MotionService, mockTransport)); motion = new MotionClient(new RobotClient('host'), motionClientName); @@ -513,9 +563,7 @@ describe('listPlanStatuses', () => { RobotClient.prototype.createServiceClient = vi .fn() - .mockImplementation(() => - createPromiseClient(MotionService, mockTransport) - ); + .mockImplementation(() => createClient(MotionService, mockTransport)); motion = new MotionClient(new RobotClient('host'), motionClientName); diff --git a/src/services/motion/types.ts b/src/services/motion/types.ts index 704fcb230..0414a5da9 100644 --- a/src/services/motion/types.ts +++ b/src/services/motion/types.ts @@ -6,6 +6,8 @@ export type CollisionSpecification = export type Constraints = PlainMessage; export type GetPlanResponse = motionApi.GetPlanResponse; export type LinearConstraint = PlainMessage; +export type PseudolinearConstraint = + PlainMessage; export type ListPlanStatusesResponse = motionApi.ListPlanStatusesResponse; export type MotionConfiguration = PlainMessage; export type ObstacleDetector = PlainMessage; @@ -18,6 +20,7 @@ export const { Constraints, GetPlanResponse, LinearConstraint, + PseudolinearConstraint, ListPlanStatusesResponse, MotionConfiguration, ObstacleDetector,