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);
+      });
+    });
+  });
+}