diff --git a/lib/src/components/generic/client.dart b/lib/src/components/generic/client.dart new file mode 100644 index 00000000000..5844980df52 --- /dev/null +++ b/lib/src/components/generic/client.dart @@ -0,0 +1,30 @@ +import 'package:grpc/grpc_connection_interface.dart'; + +import '../../gen/common/v1/common.pb.dart'; +import '../../gen/component/generic/v1/generic.pbgrpc.dart'; +import '../../resource/base.dart'; +import '../../utils.dart'; +import 'generic.dart'; + +/// gRPC client for the [Generic] component. +class GenericClient extends Generic implements ResourceRPCClient { + @override + String name; + + @override + ClientChannelBase channel; + + @override + GenericServiceClient get client => GenericServiceClient(channel); + + GenericClient(this.name, this.channel); + + @override + Future<Map<String, dynamic>> doCommand(Map<String, dynamic> command) async { + final request = DoCommandRequest() + ..name = name + ..command = command.toStruct(); + final response = await client.doCommand(request); + return response.result.toMap(); + } +} diff --git a/lib/src/components/generic/generic.dart b/lib/src/components/generic/generic.dart new file mode 100644 index 00000000000..56d30e4a252 --- /dev/null +++ b/lib/src/components/generic/generic.dart @@ -0,0 +1,18 @@ +import '../../gen/common/v1/common.pb.dart'; +import '../../resource/base.dart'; +import '../../robot/client.dart'; + +/// Generic represents a generic component that executes doCommand. +abstract class Generic extends Resource { + static const Subtype subtype = Subtype(resourceNamespaceRDK, resourceTypeComponent, 'generic'); + + /// Get the [ResourceName] for this [Generic] with the given [name] + static ResourceName getResourceName(String name) { + return Generic.subtype.getResourceName(name); + } + + /// Get the [Generic] named [name] from the provided robot. + static Generic fromRobot(RobotClient robot, String name) { + return robot.getResource(Generic.getResourceName(name)); + } +} diff --git a/lib/src/components/generic/service.dart b/lib/src/components/generic/service.dart new file mode 100644 index 00000000000..9d4aaea1662 --- /dev/null +++ b/lib/src/components/generic/service.dart @@ -0,0 +1,35 @@ +import 'package:grpc/grpc.dart'; + +import '../../gen/common/v1/common.pb.dart'; +import '../../gen/component/generic/v1/generic.pbgrpc.dart'; +import '../../resource/manager.dart'; +import '../../utils.dart'; +import 'generic.dart'; + +/// gRPC Service for a generic [Generic] +class GenericService extends GenericServiceBase { + final ResourceManager _manager; + + GenericService(this._manager); + + Generic _fromManager(String name) { + try { + return _manager.getResource(Generic.getResourceName(name)); + } catch (e) { + throw (GrpcError.notFound(e.toString())); + } + } + + @override + Future<DoCommandResponse> doCommand(ServiceCall call, DoCommandRequest request) async { + final generic = _fromManager(request.name); + final result = await generic.doCommand(request.command.toMap()); + return DoCommandResponse()..result = result.toStruct(); + } + + @override + Future<GetGeometriesResponse> getGeometries(ServiceCall call, GetGeometriesRequest request) { + // TODO: implement getGeometries + throw UnimplementedError(); + } +} diff --git a/lib/src/resource/registry.dart b/lib/src/resource/registry.dart index 27fcf2c0f44..b194e2c7af6 100644 --- a/lib/src/resource/registry.dart +++ b/lib/src/resource/registry.dart @@ -10,6 +10,8 @@ import '../components/camera/camera.dart'; import '../components/camera/client.dart'; import '../components/gantry/client.dart'; import '../components/gantry/gantry.dart'; +import '../components/generic/client.dart'; +import '../components/generic/generic.dart'; import '../components/gripper/client.dart'; import '../components/gripper/gripper.dart'; import '../components/motor/client.dart'; @@ -56,6 +58,7 @@ class Registry { registerSubtype(ResourceRegistration(Base.subtype, (name, channel) => BaseClient(name, channel))); registerSubtype(ResourceRegistration(Camera.subtype, (name, channel) => CameraClient(name, channel))); registerSubtype(ResourceRegistration(Gantry.subtype, (name, channel) => GantryClient(name, channel))); + registerSubtype(ResourceRegistration(Generic.subtype, (name, channel) => GenericClient(name, channel))); registerSubtype(ResourceRegistration(Gripper.subtype, (name, channel) => GripperClient(name, channel))); registerSubtype(ResourceRegistration(Motor.subtype, (name, channel) => MotorClient(name, channel))); registerSubtype(ResourceRegistration(MovementSensor.subtype, (name, channel) => MovementSensorClient(name, channel))); diff --git a/lib/viam_sdk.dart b/lib/viam_sdk.dart index 5138b4ec73e..f59b750c974 100644 --- a/lib/viam_sdk.dart +++ b/lib/viam_sdk.dart @@ -16,6 +16,8 @@ export 'src/components/camera/camera.dart'; export 'src/components/camera/client.dart'; export 'src/components/gantry/client.dart'; export 'src/components/gantry/gantry.dart'; +export 'src/components/generic/client.dart'; +export 'src/components/generic/generic.dart'; export 'src/components/gripper/client.dart'; export 'src/components/gripper/gripper.dart'; export 'src/components/motor/client.dart'; diff --git a/test/unit_test/components/generic_test.dart b/test/unit_test/components/generic_test.dart new file mode 100644 index 00000000000..4ae1b028a55 --- /dev/null +++ b/test/unit_test/components/generic_test.dart @@ -0,0 +1,81 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:grpc/grpc.dart'; +import 'package:viam_sdk/src/components/generic/service.dart'; +import 'package:viam_sdk/src/gen/component/generic/v1/generic.pbgrpc.dart'; +import 'package:viam_sdk/src/resource/manager.dart'; +import 'package:viam_sdk/src/utils.dart'; +import 'package:viam_sdk/viam_sdk.dart'; + +class FakeGeneric extends Generic { + @override + String name; + + FakeGeneric(this.name); + + @override + Future<Map<String, dynamic>> doCommand(Map<String, dynamic>? command) async { + return {'command': command}; + } +} + +void main() { + group('FakeGeneric Tests', () { + const String name = 'generic'; + late FakeGeneric generic; + + setUp(() { + generic = FakeGeneric(name); + }); + + test('doCommand', () async { + final cmd = {'foo': 'bar'}; + final resp = await generic.doCommand(cmd); + expect(resp['command'], cmd); + }); + }); + + group('Generic RPC Tests', () { + const String name = 'generic'; + late FakeGeneric generic; + late ClientChannel channel; + late GenericService service; + late Server server; + + setUp(() async { + final port = 50000 + (name.hashCode % 10000); + generic = FakeGeneric(name); + final ResourceManager manager = ResourceManager(); + manager.register(Generic.getResourceName(name), generic); + service = GenericService(manager); + channel = ClientChannel('localhost', port: port, options: const ChannelOptions(credentials: ChannelCredentials.insecure())); + server = Server.create(services: [service]); + await server.serve(port: port); + }); + + tearDown(() async { + await channel.shutdown(); + await server.shutdown(); + }); + + group('Generic Service Tests', () { + test('doCommand', () async { + final cmd = {'foo': 'bar'}; + + final client = GenericServiceClient(channel); + final resp = await client.doCommand(DoCommandRequest() + ..name = name + ..command = cmd.toStruct()); + expect(resp.result.toMap()['command'], cmd); + }); + }); + + group('Generic Client Tests', () { + test('doCommand', () async { + final cmd = {'foo': 'bar'}; + final client = GenericClient(name, channel); + final resp = await client.doCommand(cmd); + expect(resp['command'], cmd); + }); + }); + }); +}