From ecac847443d06e6de7a3c39bdb022ad614e434b4 Mon Sep 17 00:00:00 2001 From: gloriacai01 <55797301+gloriacai01@users.noreply.github.com> Date: Wed, 11 Sep 2024 14:40:50 -0400 Subject: [PATCH] RSDK-8714: add Servo wrappers (#60) --- .../sdk/core/component/sensor/Sensor.java | 4 - .../viam/sdk/core/component/servo/Servo.java | 94 ++++++++++++++++ .../core/component/servo/ServoRPCClient.java | 68 +++++++++++ .../core/component/servo/ServoRPCService.java | 76 +++++++++++++ .../sdk/core/resource/ResourceManager.java | 10 ++ .../component/servo/ServoRPCClientTest.kt | 92 +++++++++++++++ .../component/servo/ServoRPCServiceTest.kt | 106 ++++++++++++++++++ .../sdk/core/component/servo/ServoTest.kt | 51 +++++++++ 8 files changed, 497 insertions(+), 4 deletions(-) create mode 100644 core/sdk/src/main/java/com/viam/sdk/core/component/servo/Servo.java create mode 100644 core/sdk/src/main/java/com/viam/sdk/core/component/servo/ServoRPCClient.java create mode 100644 core/sdk/src/main/java/com/viam/sdk/core/component/servo/ServoRPCService.java create mode 100644 core/sdk/src/test/kotlin/com/viam/sdk/core/component/servo/ServoRPCClientTest.kt create mode 100644 core/sdk/src/test/kotlin/com/viam/sdk/core/component/servo/ServoRPCServiceTest.kt create mode 100644 core/sdk/src/test/kotlin/com/viam/sdk/core/component/servo/ServoTest.kt diff --git a/core/sdk/src/main/java/com/viam/sdk/core/component/sensor/Sensor.java b/core/sdk/src/main/java/com/viam/sdk/core/component/sensor/Sensor.java index 0ff1cf4fc..383dbd587 100644 --- a/core/sdk/src/main/java/com/viam/sdk/core/component/sensor/Sensor.java +++ b/core/sdk/src/main/java/com/viam/sdk/core/component/sensor/Sensor.java @@ -28,10 +28,6 @@ public Sensor(final String name) { super(SUBTYPE, named(name)); } - public Sensor(Subtype subtype, ResourceName name) { - super(subtype, name); - throw new UnsupportedOperationException(); - } /** * Get the ResourceName of the component diff --git a/core/sdk/src/main/java/com/viam/sdk/core/component/servo/Servo.java b/core/sdk/src/main/java/com/viam/sdk/core/component/servo/Servo.java new file mode 100644 index 000000000..1e0c489aa --- /dev/null +++ b/core/sdk/src/main/java/com/viam/sdk/core/component/servo/Servo.java @@ -0,0 +1,94 @@ +package com.viam.sdk.core.component.servo; + +import com.google.protobuf.Struct; +import com.viam.common.v1.Common; +import com.viam.sdk.core.component.Component; +import com.viam.sdk.core.resource.Resource; +import com.viam.sdk.core.resource.Subtype; +import com.viam.sdk.core.robot.RobotClient; + + +/** + * Servo represents a physical servo + */ +public abstract class Servo extends Component { + public static final Subtype SUBTYPE = new Subtype( + Subtype.NAMESPACE_RDK, + Subtype.RESOURCE_TYPE_COMPONENT, + "servo"); + + public Servo(final String name) { + super(SUBTYPE, named(name)); + } + + /** + * Get the ResourceName of the component + * + * @param name the name of the component + * @return the component's ResourceName + */ + public static Common.ResourceName named(final String name) { + return Resource.named(SUBTYPE, name); + } + + + /** + * Get the component with the provided name from the provided robot. + * @param robot the RobotClient + * @param name the name of the component + * @return the component + */ + public static Servo fromRobot(final RobotClient robot, final String name) { + return robot.getResource(Servo.class, named(name)); + } + + /** + * Move the servo to the provided angle. + * @param angle the desired angle of the servo in degrees + */ + public abstract void move(int angle, Struct extra); + + /** + * Move the servo to the provided angle. + * @param angle the desired angle of the servo in degrees + */ + public void move(int angle){ + move(angle, Struct.getDefaultInstance()); + } + + /** + * Get the current angle (degrees) of the servo. + * @return the current angle of the servo in degrees + */ + public abstract int getPosition(Struct extra); + + /** + * Get the current angle (degrees) of the servo. + * @return the current angle of the servo in degrees + */ + public int getPosition(){ + return getPosition(Struct.getDefaultInstance()); + } + + /** + * Stop the servo. It is assumed that the servo stops immediately. + */ + public abstract void stop(Struct extra); + + /** + * Stop the servo. It is assumed that the servo stops immediately. + */ + public void stop(){ + stop(Struct.getDefaultInstance()); + } + + + /** + * Get if the servo is currently moving. + * @return whether the servo is moving + */ + public abstract boolean isMoving(); + + +} + diff --git a/core/sdk/src/main/java/com/viam/sdk/core/component/servo/ServoRPCClient.java b/core/sdk/src/main/java/com/viam/sdk/core/component/servo/ServoRPCClient.java new file mode 100644 index 000000000..c9e2feb10 --- /dev/null +++ b/core/sdk/src/main/java/com/viam/sdk/core/component/servo/ServoRPCClient.java @@ -0,0 +1,68 @@ +package com.viam.sdk.core.component.servo; + +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import com.viam.common.v1.Common; +import com.viam.component.servo.v1.ServoServiceGrpc; + +import com.viam.sdk.core.rpc.Channel; + +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class ServoRPCClient extends Servo{ + private final ServoServiceGrpc.ServoServiceBlockingStub client; + + public ServoRPCClient(final String name, final Channel chan) { + super(name); + final ServoServiceGrpc.ServoServiceBlockingStub client = ServoServiceGrpc.newBlockingStub(chan); + if (chan.getCallCredentials().isPresent()) { + this.client = client.withCallCredentials(chan.getCallCredentials().get()); + } else { + this.client = client; + } + } + + @Override + public Struct doCommand(final Map command) { + return client.doCommand(Common.DoCommandRequest.newBuilder(). + setName(getName().getName()). + setCommand(Struct.newBuilder().putAllFields(command).build()). + build()).getResult(); + } + + @Override + public void move(int angle, Struct extra) { + com.viam.component.servo.v1.Servo.MoveRequest moveRequest = com.viam.component.servo.v1.Servo.MoveRequest.newBuilder().setName(this.getName().getName()).setAngleDeg(angle).setExtra(extra).build(); + client.move(moveRequest); + } + + @Override + public int getPosition(Struct extra) { + com.viam.component.servo.v1.Servo.GetPositionRequest getPositionRequest = com.viam.component.servo.v1.Servo.GetPositionRequest.newBuilder().setName(this.getName().getName()).setExtra(extra).build(); + return client.getPosition(getPositionRequest).getPositionDeg(); + } + + + @Override + public void stop(Struct extra) { + com.viam.component.servo.v1.Servo.StopRequest stopRequest = com.viam.component.servo.v1.Servo.StopRequest.newBuilder().setName(this.getName().getName()).setExtra(extra).build(); + client.stop(stopRequest); + } + + @Override + public boolean isMoving() { + com.viam.component.servo.v1.Servo.IsMovingRequest isMovingRequest = com.viam.component.servo.v1.Servo.IsMovingRequest.newBuilder().setName(this.getName().getName()).build(); + return client.isMoving(isMovingRequest).getIsMoving(); + } + + @Override + public List getGeometries(Optional extra){ + Common.GetGeometriesRequest.Builder builder = Common.GetGeometriesRequest.newBuilder().setName(this.getName().getName()); + extra.ifPresent(builder::setExtra); + return client.getGeometries(builder.build()).getGeometriesList(); + + } +} + diff --git a/core/sdk/src/main/java/com/viam/sdk/core/component/servo/ServoRPCService.java b/core/sdk/src/main/java/com/viam/sdk/core/component/servo/ServoRPCService.java new file mode 100644 index 000000000..5edbefb6c --- /dev/null +++ b/core/sdk/src/main/java/com/viam/sdk/core/component/servo/ServoRPCService.java @@ -0,0 +1,76 @@ +package com.viam.sdk.core.component.servo; + import com.google.protobuf.Struct; + import com.viam.common.v1.Common; + import com.viam.component.servo.v1.ServoServiceGrpc; + import com.viam.sdk.core.resource.ResourceRPCService; + import com.viam.sdk.core.resource.ResourceManager; + import io.grpc.stub.StreamObserver; + + import java.util.List; + import java.util.Optional; + + +public class ServoRPCService extends ServoServiceGrpc.ServoServiceImplBase implements ResourceRPCService { + + private final ResourceManager manager; + public ServoRPCService(ResourceManager manager) { this.manager = manager; } + @Override + public Class getResourceClass() { + return Servo.class; + } + + @Override + public ResourceManager getManager() { + return manager; + } + + @Override + public void move(com.viam.component.servo.v1.Servo.MoveRequest request, StreamObserver responseObserver) { + Servo servo = getResource(Servo.named(request.getName())); + servo.move(request.getAngleDeg(), request.getExtra()); + responseObserver.onNext(com.viam.component.servo.v1.Servo.MoveResponse.newBuilder().build()); + responseObserver.onCompleted(); + } + + @Override + public void stop(com.viam.component.servo.v1.Servo.StopRequest request, StreamObserver responseObserver) { + Servo servo = getResource(Servo.named(request.getName())); + servo.stop(); + responseObserver.onNext(com.viam.component.servo.v1.Servo.StopResponse.newBuilder().build()); + responseObserver.onCompleted(); + } + + @Override + public void getPosition(com.viam.component.servo.v1.Servo.GetPositionRequest request, StreamObserver responseObserver) { + Servo servo = getResource(Servo.named(request.getName())); + int position = servo.getPosition(request.getExtra()); + responseObserver.onNext(com.viam.component.servo.v1.Servo.GetPositionResponse.newBuilder().setPositionDeg(position).build()); + responseObserver.onCompleted(); + } + + @Override + public void isMoving(com.viam.component.servo.v1.Servo.IsMovingRequest request, StreamObserver responseObserver) { + Servo servo = getResource(Servo.named(request.getName())); + boolean isMoving = servo.isMoving(); + responseObserver.onNext(com.viam.component.servo.v1.Servo.IsMovingResponse.newBuilder().setIsMoving(isMoving).build()); + responseObserver.onCompleted(); + } + + @Override + public void getGeometries(com.viam.common.v1.Common.GetGeometriesRequest request, + StreamObserver responseObserver){ + Servo servo = getResource(Servo.named(request.getName())); + List geometries = servo.getGeometries(Optional.ofNullable(request.getExtra())); + responseObserver.onNext(Common.GetGeometriesResponse.newBuilder().addAllGeometries(geometries).build()); + responseObserver.onCompleted(); + } + @Override + public void doCommand(Common.DoCommandRequest request, + StreamObserver responseObserver) { + + Servo servo = getResource(Servo.named(request.getName())); + Struct result = servo.doCommand(request.getCommand().getFieldsMap()); + responseObserver.onNext(Common.DoCommandResponse.newBuilder().setResult(result).build()); + responseObserver.onCompleted(); + } +} diff --git a/core/sdk/src/main/java/com/viam/sdk/core/resource/ResourceManager.java b/core/sdk/src/main/java/com/viam/sdk/core/resource/ResourceManager.java index a8815d59f..f74704fdc 100644 --- a/core/sdk/src/main/java/com/viam/sdk/core/resource/ResourceManager.java +++ b/core/sdk/src/main/java/com/viam/sdk/core/resource/ResourceManager.java @@ -9,6 +9,7 @@ import com.viam.component.motor.v1.MotorServiceGrpc; import com.viam.component.movementsensor.v1.MovementSensorServiceGrpc; import com.viam.component.sensor.v1.SensorServiceGrpc; +import com.viam.component.servo.v1.ServoServiceGrpc; import com.viam.sdk.core.component.board.Board; import com.viam.sdk.core.component.board.BoardRPCClient; import com.viam.sdk.core.component.board.BoardRPCService; @@ -30,6 +31,9 @@ import com.viam.sdk.core.component.sensor.Sensor; import com.viam.sdk.core.component.sensor.SensorRPCClient; import com.viam.sdk.core.component.sensor.SensorRPCService; +import com.viam.sdk.core.component.servo.Servo; +import com.viam.sdk.core.component.servo.ServoRPCClient; +import com.viam.sdk.core.component.servo.ServoRPCService; import com.viam.sdk.core.exception.DuplicateResourceException; import com.viam.sdk.core.exception.ResourceNotFoundException; import com.viam.sdk.core.service.datamanager.DataManager; @@ -95,6 +99,12 @@ public class ResourceManager implements Closeable { SensorRPCService::new, SensorRPCClient::new )); + Registry.registerSubtype(new ResourceRegistration<>( + Servo.SUBTYPE, + ServoServiceGrpc.SERVICE_NAME, + ServoRPCService::new, + ServoRPCClient::new + )); // SERVICES Registry.registerSubtype(new ResourceRegistration<>( diff --git a/core/sdk/src/test/kotlin/com/viam/sdk/core/component/servo/ServoRPCClientTest.kt b/core/sdk/src/test/kotlin/com/viam/sdk/core/component/servo/ServoRPCClientTest.kt new file mode 100644 index 000000000..917142f43 --- /dev/null +++ b/core/sdk/src/test/kotlin/com/viam/sdk/core/component/servo/ServoRPCClientTest.kt @@ -0,0 +1,92 @@ +package com.viam.sdk.core.component.servo + +import com.google.protobuf.Struct +import com.google.protobuf.Value +import com.viam.common.v1.Common.Geometry +import com.viam.sdk.core.resource.ResourceManager +import com.viam.sdk.core.rpc.BasicManagedChannel +import io.grpc.inprocess.InProcessChannelBuilder +import io.grpc.inprocess.InProcessServerBuilder +import io.grpc.testing.GrpcCleanupRule +import org.junit.Rule +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mockito.* +import java.util.* + +class ServoRPCClientTest { + private lateinit var servo: Servo + private lateinit var client: ServoRPCClient + + @JvmField + @Rule + val grpcCleanupRule: GrpcCleanupRule = GrpcCleanupRule() + + @BeforeEach + fun setup() { + servo = mock( + Servo::class.java, withSettings().useConstructor("mock-servo").defaultAnswer( + CALLS_REAL_METHODS + ) + ) + val resourceManager = ResourceManager(listOf(servo)) + val service = ServoRPCService(resourceManager) + val serviceName = InProcessServerBuilder.generateName() + grpcCleanupRule.register( + InProcessServerBuilder.forName(serviceName).directExecutor().addService(service).build().start() + ) + val channel = grpcCleanupRule.register(InProcessChannelBuilder.forName(serviceName).directExecutor().build()) + client = ServoRPCClient("mock-servo", BasicManagedChannel(channel)) + } + + @Test + fun move(){ + val extra = + Struct.newBuilder().putAllFields(mapOf("foo" to Value.newBuilder().setStringValue("bar").build())).build() + client.move(20, extra) + verify(servo).move(20, extra) + + } + + @Test + fun stop() { + client.stop() + verify(servo).stop(Struct.getDefaultInstance()) + } + + @Test + fun isMoving() { + `when`(servo.isMoving()).thenReturn(false) + val isMoving = client.isMoving() + verify(servo).isMoving() + assertFalse(isMoving) + } + + @Test + fun getGeometries() { + doReturn(listOf()).`when`(servo).getGeometries(any()) + client.getGeometries(Optional.empty()) + verify(servo).getGeometries(any()) + } + + @Test + fun getPosition() { + `when`(servo.getPosition(any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn(80) + val pos = client.getPosition() + verify(servo).getPosition(Struct.getDefaultInstance()) + assertEquals(80, pos) + } + + @Test + fun doCommand() { + val command = mapOf("foo" to Value.newBuilder().setStringValue("bar").build()) + doReturn(Struct.newBuilder().putAllFields(command).build()).`when`(servo).doCommand(anyMap()) + val response = client.doCommand(command) + verify(servo).doCommand(command) + assertEquals(command, response.fieldsMap) + } + + +} diff --git a/core/sdk/src/test/kotlin/com/viam/sdk/core/component/servo/ServoRPCServiceTest.kt b/core/sdk/src/test/kotlin/com/viam/sdk/core/component/servo/ServoRPCServiceTest.kt new file mode 100644 index 000000000..cc7ce585b --- /dev/null +++ b/core/sdk/src/test/kotlin/com/viam/sdk/core/component/servo/ServoRPCServiceTest.kt @@ -0,0 +1,106 @@ +package com.viam.sdk.core.component.servo + +import com.google.protobuf.Struct +import com.google.protobuf.Value +import com.viam.common.v1.Common +import com.viam.common.v1.Common.Geometry +import com.viam.component.servo.v1.Servo.StopRequest +import com.viam.component.servo.v1.Servo.GetPositionRequest +import com.viam.component.servo.v1.Servo.IsMovingRequest +import com.viam.component.servo.v1.ServoServiceGrpc +import com.viam.component.servo.v1.ServoServiceGrpc.ServoServiceBlockingStub +import com.viam.sdk.core.resource.ResourceManager +import io.grpc.inprocess.InProcessChannelBuilder +import io.grpc.inprocess.InProcessServerBuilder +import io.grpc.testing.GrpcCleanupRule +import org.junit.Rule +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mockito.* +import java.util.* + +class ServoRPCServiceTest { + private lateinit var servo: Servo + private lateinit var client: ServoServiceBlockingStub + + @JvmField + @Rule + val grpcCleanupRule: GrpcCleanupRule = GrpcCleanupRule() + + @BeforeEach + fun setup() { + servo = mock( + Servo::class.java, withSettings().useConstructor("mock-servo").defaultAnswer( + CALLS_REAL_METHODS + ) + ) + + val resourceManager = ResourceManager(listOf(servo)) + val service = ServoRPCService(resourceManager) + val serviceName = InProcessServerBuilder.generateName() + grpcCleanupRule.register( + InProcessServerBuilder.forName(serviceName).directExecutor().addService(service).build().start() + ) + client = ServoServiceGrpc.newBlockingStub( + grpcCleanupRule.register( + InProcessChannelBuilder.forName(serviceName).build() + ) + ) + } + + @Test + fun move(){ + val extra = + Struct.newBuilder().putAllFields(mapOf("foo" to Value.newBuilder().setStringValue("bar").build())).build() + val request = com.viam.component.servo.v1.Servo.MoveRequest.newBuilder().setName(servo.name.name).setAngleDeg(20).setExtra(extra).build() + client.move(request) + verify(servo).move(20, extra) + } + + @Test + fun isMoving(){ + `when`(servo.isMoving()).thenReturn(false) + val request = IsMovingRequest.newBuilder().setName(servo.name.name).build() + val response = client.isMoving(request) + verify(servo).isMoving() + assertFalse(response.isMoving) + } + + @Test + fun getPosition(){ + `when`(servo.getPosition(any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn(80) + val request = GetPositionRequest.newBuilder().setName(servo.name.name).build() + val response = client.getPosition(request) + verify(servo).getPosition(Struct.getDefaultInstance()) + assertEquals(80, response.positionDeg) + } + + @Test + fun stop(){ + val request = StopRequest.newBuilder().setName(servo.name.name).build() + client.stop(request) + verify(servo).stop() + } + + @Test + fun getGeometries(){ + doReturn(listOf()).`when`(servo).getGeometries(any()) + val request = Common.GetGeometriesRequest.newBuilder().setName(servo.name.name).build() + client.getGeometries(request) + verify(servo).getGeometries(Optional.of(Struct.getDefaultInstance())) + } + + @Test + fun doCommand(){ + val command = + Struct.newBuilder().putAllFields(mapOf("foo" to Value.newBuilder().setStringValue("bar").build())).build() + doReturn(command).`when`(servo).doCommand(anyMap()) + val request = Common.DoCommandRequest.newBuilder().setName(servo.name.name).setCommand(command).build() + val response = client.doCommand(request) + verify(servo).doCommand(command.fieldsMap) + assertEquals(command, response.result) + } + +} \ No newline at end of file diff --git a/core/sdk/src/test/kotlin/com/viam/sdk/core/component/servo/ServoTest.kt b/core/sdk/src/test/kotlin/com/viam/sdk/core/component/servo/ServoTest.kt new file mode 100644 index 000000000..ce33ef860 --- /dev/null +++ b/core/sdk/src/test/kotlin/com/viam/sdk/core/component/servo/ServoTest.kt @@ -0,0 +1,51 @@ +package com.viam.sdk.core.component.servo + +import com.google.protobuf.Struct +import com.google.protobuf.Value +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Answers +import org.mockito.Mockito.* + +class ServoTest { + private lateinit var servo: Servo + + @BeforeEach + fun setup() { + servo = mock(Servo::class.java, Answers.CALLS_REAL_METHODS) + } + @Test + fun move(){ + val extra = + Struct.newBuilder().putAllFields(mapOf("foo" to Value.newBuilder().setStringValue("bar").build())).build() + servo.move(20, extra) + verify(servo).move(20, extra) + } + @Test + fun isMoving(){ + `when`(servo.isMoving()).thenReturn(false) + val isMoving = servo.isMoving() + verify(servo).isMoving() + assertFalse(isMoving) + + } + + @Test + fun getPosition(){ + val extra = + Struct.newBuilder().putAllFields(mapOf("foo" to Value.newBuilder().setStringValue("bar").build())).build() + `when`(servo.getPosition(any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn(80) + val pos = servo.getPosition(extra) + verify(servo).getPosition(extra) + assertEquals(80, pos) + + } + + @Test + fun stop(){ + servo.stop() + verify(servo).stop() + } + +} \ No newline at end of file