From 5fb10106bc5066bfa3f139ed88e0c8be1c608f68 Mon Sep 17 00:00:00 2001 From: Naveed Jooma Date: Tue, 14 May 2024 11:52:39 -0400 Subject: [PATCH] Motor (#29) --- core/sdk/build.gradle | 1 + .../viam/sdk/core/component/board/Board.kt | 3 + .../core/component/board/BoardRPCService.kt | 82 ++++---- .../viam/sdk/core/component/motor/Motor.kt | 180 ++++++++++++++++++ .../core/component/motor/MotorRPCClient.kt | 93 +++++++++ .../core/component/motor/MotorRPCService.kt | 127 ++++++++++++ .../sdk/core/resource/ResourceManager.java | 12 +- .../component/motor/MotorRPCClientTest.kt | 121 ++++++++++++ .../component/motor/MotorRPCServiceTest.kt | 146 ++++++++++++++ .../sdk/core/component/motor/MotorTest.kt | 81 ++++++++ 10 files changed, 802 insertions(+), 44 deletions(-) create mode 100644 core/sdk/src/main/java/com/viam/sdk/core/component/motor/Motor.kt create mode 100644 core/sdk/src/main/java/com/viam/sdk/core/component/motor/MotorRPCClient.kt create mode 100644 core/sdk/src/main/java/com/viam/sdk/core/component/motor/MotorRPCService.kt create mode 100644 core/sdk/src/test/java/com/viam/sdk/core/component/motor/MotorRPCClientTest.kt create mode 100644 core/sdk/src/test/java/com/viam/sdk/core/component/motor/MotorRPCServiceTest.kt create mode 100644 core/sdk/src/test/java/com/viam/sdk/core/component/motor/MotorTest.kt diff --git a/core/sdk/build.gradle b/core/sdk/build.gradle index 7a631b70a..b779c6467 100644 --- a/core/sdk/build.gradle +++ b/core/sdk/build.gradle @@ -26,6 +26,7 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testImplementation "io.grpc:grpc-testing:1.63.0" testImplementation "io.grpc:grpc-inprocess:1.63.0" + testImplementation 'org.mockito:mockito-core:5.12.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } diff --git a/core/sdk/src/main/java/com/viam/sdk/core/component/board/Board.kt b/core/sdk/src/main/java/com/viam/sdk/core/component/board/Board.kt index 1ac843dbd..d262ac9ce 100644 --- a/core/sdk/src/main/java/com/viam/sdk/core/component/board/Board.kt +++ b/core/sdk/src/main/java/com/viam/sdk/core/component/board/Board.kt @@ -20,6 +20,7 @@ typealias Tick = StreamTicksResponse */ abstract class Board(name: String) : Component(SUBTYPE, named(name)) { companion object { + @JvmStatic val SUBTYPE = Subtype(Subtype.NAMESPACE_RDK, Subtype.RESOURCE_TYPE_COMPONENT, "board") @@ -28,6 +29,7 @@ abstract class Board(name: String) : Component(SUBTYPE, named(name)) { * @param name the name of the component * @return the component's ResourceName */ + @JvmStatic fun named(name: String): ResourceName { return Resource.named(SUBTYPE, name) } @@ -38,6 +40,7 @@ abstract class Board(name: String) : Component(SUBTYPE, named(name)) { * @param name the name of the component * @return the component */ + @JvmStatic fun fromRobot(robot: RobotClient, name: String): Board { return robot.getResource(Board::class.java, named(name)) } diff --git a/core/sdk/src/main/java/com/viam/sdk/core/component/board/BoardRPCService.kt b/core/sdk/src/main/java/com/viam/sdk/core/component/board/BoardRPCService.kt index 40730ce2b..8cc83df18 100644 --- a/core/sdk/src/main/java/com/viam/sdk/core/component/board/BoardRPCService.kt +++ b/core/sdk/src/main/java/com/viam/sdk/core/component/board/BoardRPCService.kt @@ -3,6 +3,7 @@ package com.viam.sdk.core.component.board import com.viam.common.v1.Common import com.viam.common.v1.Common.DoCommandResponse import com.viam.common.v1.Common.GetGeometriesResponse +import com.viam.component.board.v1.Board.* import com.viam.component.board.v1.BoardServiceGrpc import com.viam.sdk.core.resource.ResourceManager import com.viam.sdk.core.resource.ResourceRPCService @@ -12,110 +13,108 @@ import java.util.* import kotlin.time.DurationUnit import kotlin.time.toDuration -class BoardRPCService(private val manager: ResourceManager) : - BoardServiceGrpc.BoardServiceImplBase(), ResourceRPCService { +class BoardRPCService(private val manager: ResourceManager) : BoardServiceGrpc.BoardServiceImplBase(), + ResourceRPCService { override fun setGPIO( - request: com.viam.component.board.v1.Board.SetGPIORequest, - responseObserver: StreamObserver + request: SetGPIORequest, + responseObserver: StreamObserver ) { val board = getResource(Board.named(request.name)) board.setGpioState(request.pin, request.high, Optional.of(request.extra)) responseObserver.onNext( - com.viam.component.board.v1.Board.SetGPIOResponse.newBuilder().build() + SetGPIOResponse.newBuilder().build() ) responseObserver.onCompleted() } override fun getGPIO( - request: com.viam.component.board.v1.Board.GetGPIORequest, - responseObserver: StreamObserver + request: GetGPIORequest, + responseObserver: StreamObserver ) { val board = getResource(Board.named(request.name)) val state = board.getGpioState(request.pin, Optional.of(request.extra)) responseObserver.onNext( - com.viam.component.board.v1.Board.GetGPIOResponse.newBuilder().setHigh(state).build() + GetGPIOResponse.newBuilder().setHigh(state).build() ) responseObserver.onCompleted() } override fun setPWM( - request: com.viam.component.board.v1.Board.SetPWMRequest, - responseObserver: StreamObserver + request: SetPWMRequest, + responseObserver: StreamObserver ) { val board = getResource(Board.named(request.name)) board.setPwm(request.pin, request.dutyCyclePct, Optional.of(request.extra)) responseObserver.onNext( - com.viam.component.board.v1.Board.SetPWMResponse.newBuilder().build() + SetPWMResponse.newBuilder().build() ) responseObserver.onCompleted() } override fun pWM( - request: com.viam.component.board.v1.Board.PWMRequest, - responseObserver: StreamObserver + request: PWMRequest, + responseObserver: StreamObserver ) { val board = getResource(Board.named(request.name)) val pwm = board.getPwm(request.pin, Optional.of(request.extra)) responseObserver.onNext( - com.viam.component.board.v1.Board.PWMResponse.newBuilder().setDutyCyclePct(pwm).build() + PWMResponse.newBuilder().setDutyCyclePct(pwm).build() ) responseObserver.onCompleted() } override fun setPWMFrequency( - request: com.viam.component.board.v1.Board.SetPWMFrequencyRequest, - responseObserver: StreamObserver + request: SetPWMFrequencyRequest, + responseObserver: StreamObserver ) { val board = getResource(Board.named(request.name)) board.setPwmFrequency(request.pin, request.frequencyHz.toInt(), Optional.of(request.extra)) responseObserver.onNext( - com.viam.component.board.v1.Board.SetPWMFrequencyResponse.newBuilder().build() + SetPWMFrequencyResponse.newBuilder().build() ) responseObserver.onCompleted() } override fun pWMFrequency( - request: com.viam.component.board.v1.Board.PWMFrequencyRequest, - responseObserver: StreamObserver + request: PWMFrequencyRequest, + responseObserver: StreamObserver ) { val board = getResource(Board.named(request.name)) val freq = board.getPwmFrequency(request.pin, Optional.of(request.extra)) responseObserver.onNext( - com.viam.component.board.v1.Board.PWMFrequencyResponse.newBuilder() - .setFrequencyHz(freq.toLong()).build() + PWMFrequencyResponse.newBuilder().setFrequencyHz(freq.toLong()).build() ) responseObserver.onCompleted() } override fun writeAnalog( - request: com.viam.component.board.v1.Board.WriteAnalogRequest, - responseObserver: StreamObserver + request: WriteAnalogRequest, + responseObserver: StreamObserver ) { val board = getResource(Board.named(request.name)) board.writeAnalog(request.pin, request.value, Optional.of(request.extra)) responseObserver.onNext( - com.viam.component.board.v1.Board.WriteAnalogResponse.newBuilder().build() + WriteAnalogResponse.newBuilder().build() ) responseObserver.onCompleted() } override fun readAnalogReader( - request: com.viam.component.board.v1.Board.ReadAnalogReaderRequest, - responseObserver: StreamObserver + request: ReadAnalogReaderRequest, + responseObserver: StreamObserver ) { val board = getResource(Board.named(request.boardName)) val value = board.getAnalogReaderValue(request.analogReaderName, Optional.of(request.extra)) responseObserver.onNext( - com.viam.component.board.v1.Board.ReadAnalogReaderResponse.newBuilder().setValue(value) - .build() + ReadAnalogReaderResponse.newBuilder().setValue(value).build() ) responseObserver.onCompleted() } override fun streamTicks( - request: com.viam.component.board.v1.Board.StreamTicksRequest, - responseObserver: StreamObserver + request: StreamTicksRequest, + responseObserver: StreamObserver ) { val board = getResource(Board.named(request.name)) val ticksStream = board.streamTicks(request.pinNamesList, Optional.of(request.extra)) @@ -126,8 +125,8 @@ class BoardRPCService(private val manager: ResourceManager) : } override fun setPowerMode( - request: com.viam.component.board.v1.Board.SetPowerModeRequest, - responseObserver: StreamObserver + request: SetPowerModeRequest, + responseObserver: StreamObserver ) { val board = getResource(Board.named(request.name)) board.setPowerMode( @@ -136,28 +135,26 @@ class BoardRPCService(private val manager: ResourceManager) : Optional.of(request.extra) ) responseObserver.onNext( - com.viam.component.board.v1.Board.SetPowerModeResponse.newBuilder().build() + SetPowerModeResponse.newBuilder().build() ) responseObserver.onCompleted() } override fun getDigitalInterruptValue( - request: com.viam.component.board.v1.Board.GetDigitalInterruptValueRequest, - responseObserver: StreamObserver + request: GetDigitalInterruptValueRequest, + responseObserver: StreamObserver ) { val board = getResource(Board.named(request.boardName)) - val value = - board.getDigitalInterruptValue(request.digitalInterruptName, Optional.of(request.extra)) + val value = board.getDigitalInterruptValue(request.digitalInterruptName, Optional.of(request.extra)) responseObserver.onNext( - com.viam.component.board.v1.Board.GetDigitalInterruptValueResponse.newBuilder() - .setValue(value.toLong()).build() + GetDigitalInterruptValueResponse.newBuilder().setValue(value.toLong()) + .build() ) responseObserver.onCompleted() } override fun doCommand( - request: Common.DoCommandRequest, - responseObserver: StreamObserver + request: Common.DoCommandRequest, responseObserver: StreamObserver ) { val board = getResource(Board.named(request.name)) val result = board.doCommand(request.command.fieldsMap) @@ -166,8 +163,7 @@ class BoardRPCService(private val manager: ResourceManager) : } override fun getGeometries( - request: Common.GetGeometriesRequest, - responseObserver: StreamObserver + request: Common.GetGeometriesRequest, responseObserver: StreamObserver ) { val board = getResource(Board.named(request.name)) val result = board.getGeometries(Optional.of(request.extra)) diff --git a/core/sdk/src/main/java/com/viam/sdk/core/component/motor/Motor.kt b/core/sdk/src/main/java/com/viam/sdk/core/component/motor/Motor.kt new file mode 100644 index 000000000..b0b274568 --- /dev/null +++ b/core/sdk/src/main/java/com/viam/sdk/core/component/motor/Motor.kt @@ -0,0 +1,180 @@ +package com.viam.sdk.core.component.motor + +import com.google.protobuf.Struct +import com.viam.common.v1.Common.ResourceName +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 +import java.util.* + +/** + * A [Motor] represents a physical motor + */ +abstract class Motor(name: String) : Component(SUBTYPE, named(name)) { + + @JvmInline + value class Properties(val positionReporting: Boolean) + + companion object { + @JvmStatic + val SUBTYPE = Subtype(Subtype.NAMESPACE_RDK, Subtype.RESOURCE_TYPE_COMPONENT, "motor") + + /** + * Get the ResourceName of the component + * @param name the name of the component + * @return the component's ResourceName + */ + @JvmStatic + fun named(name: String): ResourceName { + 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 + */ + @JvmStatic + fun fromRobot(robot: RobotClient, name: String): Motor { + return robot.getResource(Motor::class.java, named(name)) + } + } + + /** + * Sets the "percentage" of power the motor should employ between -1 and 1. + * When [power] is negative, the rotation will be in the backward direction. + * @param power the power percent, between -1 and 1 (negative indicating backwards rotation) + */ + abstract fun setPower(power: Double, extra: Struct) + + /** + * Sets the "percentage" of power the motor should employ between -1 and 1. + * When [power] is negative, the rotation will be in the backward direction. + * @param power the power percent, between -1 and 1 (negative indicating backwards rotation) + */ + fun setPower(power: Double) { + setPower(power, Struct.getDefaultInstance()) + } + + /** + * Spin the motor the specified number of [revolutions] at specified [rpm]. + * When [rpm] or [revolutions] is a negative value, the rotation will be in the backward direction. + * Note: if both [rpm] and [revolutions] are negative, the motor will spin in the forward direction. + * @param rpm speed at which the motor should move in rotations per minute (negative implies backwards) + * @param revolutions number of revolutions the motor should run for (negative implies backwards) + */ + abstract fun goFor(rpm: Double, revolutions: Double, extra: Struct) + + /** + * Spin the motor the specified number of [revolutions] at specified [rpm]. + * When [rpm] or [revolutions] is a negative value, the rotation will be in the backward direction. + * Note: if both [rpm] and [revolutions] are negative, the motor will spin in the forward direction. + * @param rpm speed at which the motor should move in rotations per minute (negative implies backwards) + * @param revolutions number of revolutions the motor should run for (negative implies backwards) + */ + fun goFor(rpm: Double, revolutions: Double) { + goFor(rpm, revolutions, Struct.getDefaultInstance()) + } + + /** + * Spin the motor to the specified position (provided in revolutions from home/zero), + * at the specified speed, in revolutions per minute. + * Regardless of the directionality of the [rpm] this function will move + * the motor towards the specified position. + * @param rpm speed at which the motor should move in rotations per minute + * @param positionRevolutions target position relative to home/zero, in revolutions + */ + abstract fun goTo(rpm: Double, positionRevolutions: Double, extra: Struct) + + /** + * Spin the motor to the specified position (provided in revolutions from home/zero), + * at the specified speed, in revolutions per minute. + * Regardless of the directionality of the [rpm] this function will move + * the motor towards the specified position. + * @param rpm speed at which the motor should move in rotations per minute + * @param positionRevolutions target position relative to home/zero, in revolutions + */ + fun goTo(rpm: Double, positionRevolutions: Double) { + goTo(rpm, positionRevolutions, Struct.getDefaultInstance()) + } + + /** + * Set the current position (modified by [offset]) to be the new zero (home) position. + * @param offset the offset from the current position to new home/zero position + */ + abstract fun resetZeroPosition(offset: Double, extra: Struct) + + /** + * Set the current position (modified by [offset]) to be the new zero (home) position. + * @param offset the offset from the current position to new home/zero position + */ + fun resetZeroPosition(offset: Double) { + resetZeroPosition(offset, Struct.getDefaultInstance()) + } + + /** + * Report the position of the motor based on its encoder. + * The value returned is the number of revolutions relative to its zero position. + * This method will raise an exception if position reporting is not supported by the motor. + * @returns number of revolutions the motor is away from zero/home + */ + abstract fun getPosition(extra: Struct): Double + + /** + * Report the position of the motor based on its encoder. + * The value returned is the number of revolutions relative to its zero position. + * This method will raise an exception if position reporting is not supported by the motor. + * @returns number of revolutions the motor is away from zero/home + */ + fun getPosition(): Double { + return getPosition(Struct.getDefaultInstance()) + } + + /** + * Report which optional properties are supported by this motor + * @returns the optional features and if they are supported by this motor + */ + abstract fun getProperties(extra: Struct): Properties + + /** + * Report which optional properties are supported by this motor + * @returns the optional features and if they are supported by this motor + */ + fun getProperties(): Properties { + return getProperties(Struct.getDefaultInstance()) + } + + /** + * Stop the motor immediately, without any gradual step down. + */ + abstract fun stop(extra: Struct) + + /** + * Stop the motor immediately, without any gradual step down. + */ + fun stop() { + stop(Struct.getDefaultInstance()) + } + + /** + * Check to see if the motor is currently running and its current power level. + * @returns if the motor is currently powered and its power level + */ + abstract fun isPowered(extra: Struct): Pair + + /** + * Check to see if the motor is currently running and its current power level. + * @returns if the motor is currently powered and its power level + */ + fun isPowered(): Pair { + return isPowered(Struct.getDefaultInstance()) + } + + /** + * Get if the motor is currently moving. + * @returns if the motor is moving + */ + abstract fun isMoving(): Boolean +} diff --git a/core/sdk/src/main/java/com/viam/sdk/core/component/motor/MotorRPCClient.kt b/core/sdk/src/main/java/com/viam/sdk/core/component/motor/MotorRPCClient.kt new file mode 100644 index 000000000..bf63fe5d8 --- /dev/null +++ b/core/sdk/src/main/java/com/viam/sdk/core/component/motor/MotorRPCClient.kt @@ -0,0 +1,93 @@ +package com.viam.sdk.core.component.motor + +import com.google.protobuf.Struct +import com.google.protobuf.Value +import com.viam.common.v1.Common +import com.viam.common.v1.Common.GetGeometriesRequest +import com.viam.component.motor.v1.Motor.* +import com.viam.component.motor.v1.MotorServiceGrpc +import com.viam.component.motor.v1.MotorServiceGrpc.MotorServiceBlockingStub +import com.viam.sdk.core.rpc.Channel +import java.util.* +import kotlin.jvm.optionals.getOrDefault + +class MotorRPCClient(name: String, channel: Channel) : Motor(name) { + private val client: MotorServiceBlockingStub + + init { + val client = MotorServiceGrpc.newBlockingStub(channel) + if (channel.callCredentials.isPresent) { + this.client = client.withCallCredentials(channel.callCredentials.get()) + } else { + this.client = client + } + } + + override fun setPower(power: Double, extra: Struct) { + val request = SetPowerRequest.newBuilder().setName(this.name.name).setPowerPct(power).setExtra(extra).build() + this.client.setPower(request) + } + + override fun goFor(rpm: Double, revolutions: Double, extra: Struct) { + val request = + GoForRequest.newBuilder().setName(this.name.name).setRpm(rpm).setRevolutions(revolutions).setExtra(extra) + .build() + this.client.goFor(request) + } + + override fun goTo(rpm: Double, positionRevolutions: Double, extra: Struct) { + val request = + GoToRequest.newBuilder().setName(this.name.name).setRpm(rpm).setPositionRevolutions(positionRevolutions) + .setExtra(extra).build() + this.client.goTo(request) + } + + override fun resetZeroPosition(offset: Double, extra: Struct) { + val request = + ResetZeroPositionRequest.newBuilder().setName(this.name.name).setOffset(offset).setExtra(extra).build() + this.client.resetZeroPosition(request) + } + + override fun getPosition(extra: Struct): Double { + val request = GetPositionRequest.newBuilder().setName(this.name.name).setExtra(extra).build() + val response = this.client.getPosition(request) + return response.position + } + + override fun getProperties(extra: Struct): Properties { + val request = GetPropertiesRequest.newBuilder().setName(this.name.name).setExtra(extra).build() + val response = this.client.getProperties(request) + return Properties(positionReporting = response.positionReporting) + } + + override fun stop(extra: Struct) { + val request = StopRequest.newBuilder().setName(this.name.name).setExtra(extra).build() + this.client.stop(request) + } + + override fun isPowered(extra: Struct): Pair { + val request = IsPoweredRequest.newBuilder().setName(this.name.name).setExtra(extra).build() + val response = this.client.isPowered(request) + return Pair(response.isOn, response.powerPct) + } + + override fun isMoving(): Boolean { + val request = IsMovingRequest.newBuilder().setName(this.name.name).build() + val response = this.client.isMoving(request) + return response.isMoving + } + + override fun doCommand(command: Map?): Struct { + val request = Common.DoCommandRequest.newBuilder().setName(this.name.name) + .setCommand(Struct.newBuilder().putAllFields(command).build()).build() + val response = this.client.doCommand(request) + return response.result + } + + override fun getGeometries(extra: Optional): List { + val request = GetGeometriesRequest.newBuilder().setName(this.name.name) + .setExtra(extra.getOrDefault(Struct.getDefaultInstance())).build() + val response = this.client.getGeometries(request) + return response.geometriesList + } +} diff --git a/core/sdk/src/main/java/com/viam/sdk/core/component/motor/MotorRPCService.kt b/core/sdk/src/main/java/com/viam/sdk/core/component/motor/MotorRPCService.kt new file mode 100644 index 000000000..30ab7aa64 --- /dev/null +++ b/core/sdk/src/main/java/com/viam/sdk/core/component/motor/MotorRPCService.kt @@ -0,0 +1,127 @@ +package com.viam.sdk.core.component.motor + +import com.viam.common.v1.Common.* +import com.viam.component.motor.v1.Motor.* +import com.viam.component.motor.v1.MotorServiceGrpc +import com.viam.sdk.core.resource.ResourceManager +import com.viam.sdk.core.resource.ResourceRPCService +import io.grpc.stub.StreamObserver +import java.util.* + +class MotorRPCService(private val manager: ResourceManager) : MotorServiceGrpc.MotorServiceImplBase(), + ResourceRPCService { + + override fun setPower( + request: SetPowerRequest, responseObserver: StreamObserver + ) { + val motor = getResource(Motor.named(request.name)) + motor.setPower(request.powerPct, request.extra) + responseObserver.onNext(SetPowerResponse.newBuilder().build()) + responseObserver.onCompleted() + } + + override fun goFor( + request: GoForRequest, responseObserver: StreamObserver + ) { + val motor = getResource(Motor.named(request.name)) + motor.goFor(request.rpm, request.revolutions, request.extra) + responseObserver.onNext(GoForResponse.newBuilder().build()) + responseObserver.onCompleted() + } + + override fun goTo( + request: GoToRequest, responseObserver: StreamObserver + ) { + val motor = getResource(Motor.named(request.name)) + motor.goTo(request.rpm, request.positionRevolutions, request.extra) + responseObserver.onNext(GoToResponse.newBuilder().build()) + responseObserver.onCompleted() + } + + override fun resetZeroPosition( + request: ResetZeroPositionRequest, responseObserver: StreamObserver + ) { + val motor = getResource(Motor.named(request.name)) + motor.resetZeroPosition(request.offset, request.extra) + responseObserver.onNext(ResetZeroPositionResponse.newBuilder().build()) + responseObserver.onCompleted() + } + + override fun getPosition( + request: GetPositionRequest, responseObserver: StreamObserver + ) { + val motor = getResource(Motor.named(request.name)) + val position = motor.getPosition(request.extra) + responseObserver.onNext(GetPositionResponse.newBuilder().setPosition(position).build()) + responseObserver.onCompleted() + } + + override fun getProperties( + request: GetPropertiesRequest, responseObserver: StreamObserver + ) { + val motor = getResource(Motor.named(request.name)) + val properties = motor.getProperties(request.extra) + responseObserver.onNext( + GetPropertiesResponse.newBuilder().setPositionReporting(properties.positionReporting).build() + ) + responseObserver.onCompleted() + } + + override fun stop( + request: StopRequest, responseObserver: StreamObserver + ) { + val motor = getResource(Motor.named(request.name)) + motor.stop(request.extra) + responseObserver.onNext(StopResponse.newBuilder().build()) + responseObserver.onCompleted() + } + + override fun isPowered( + request: IsPoweredRequest, responseObserver: StreamObserver + ) { + val motor = getResource(Motor.named(request.name)) + val isPoweredResponse = motor.isPowered(request.extra) + responseObserver.onNext( + IsPoweredResponse.newBuilder().setIsOn(isPoweredResponse.first).setPowerPct(isPoweredResponse.second) + .build() + ) + responseObserver.onCompleted() + } + + override fun isMoving( + request: IsMovingRequest, responseObserver: StreamObserver + ) { + val motor = getResource(Motor.named(request.name)) + val isMoving = motor.isMoving() + responseObserver.onNext( + IsMovingResponse.newBuilder().setIsMoving(isMoving).build() + ) + responseObserver.onCompleted() + } + + override fun doCommand( + request: DoCommandRequest, responseObserver: StreamObserver + ) { + val motor = getResource(Motor.named(request.name)) + val result = motor.doCommand(request.command.fieldsMap) + responseObserver.onNext(DoCommandResponse.newBuilder().setResult(result).build()) + responseObserver.onCompleted() + } + + override fun getGeometries( + request: GetGeometriesRequest, responseObserver: StreamObserver + ) { + val motor = getResource(Motor.named(request.name)) + val result = motor.getGeometries(Optional.of(request.extra)) + responseObserver.onNext(GetGeometriesResponse.newBuilder().addAllGeometries(result).build()) + responseObserver.onCompleted() + } + + override fun getResourceClass(): Class { + return Motor::class.java + } + + override fun getManager(): ResourceManager { + return this.manager + } +} 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 e1b58e676..eb432bda3 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 @@ -6,6 +6,7 @@ import com.viam.component.camera.v1.CameraServiceGrpc; import com.viam.component.generic.v1.GenericServiceGrpc; import com.viam.component.gripper.v1.GripperServiceGrpc; +import com.viam.component.motor.v1.MotorServiceGrpc; import com.viam.component.movementsensor.v1.MovementSensorServiceGrpc; import com.viam.component.sensor.v1.SensorServiceGrpc; import com.viam.sdk.core.component.board.Board; @@ -20,6 +21,9 @@ import com.viam.sdk.core.component.gripper.Gripper; import com.viam.sdk.core.component.gripper.GripperRPCClient; import com.viam.sdk.core.component.gripper.GripperRPCService; +import com.viam.sdk.core.component.motor.Motor; +import com.viam.sdk.core.component.motor.MotorRPCClient; +import com.viam.sdk.core.component.motor.MotorRPCService; import com.viam.sdk.core.component.movementsensor.MovementSensor; import com.viam.sdk.core.component.movementsensor.MovementSensorRPCClient; import com.viam.sdk.core.component.movementsensor.MovementSensorRPCService; @@ -50,7 +54,7 @@ public class ResourceManager implements Closeable { // register well-known subtypes // COMPONENTS Registry.registerSubtype(new ResourceRegistration<>( - Board.Companion.getSUBTYPE(), + Board.getSUBTYPE(), BoardServiceGrpc.SERVICE_NAME, BoardRPCService::new, BoardRPCClient::new @@ -73,6 +77,12 @@ public class ResourceManager implements Closeable { GripperRPCService::new, GripperRPCClient::new )); + Registry.registerSubtype(new ResourceRegistration<>( + Motor.getSUBTYPE(), + MotorServiceGrpc.SERVICE_NAME, + MotorRPCService::new, + MotorRPCClient::new + )); Registry.registerSubtype(new ResourceRegistration<>( MovementSensor.SUBTYPE, MovementSensorServiceGrpc.SERVICE_NAME, diff --git a/core/sdk/src/test/java/com/viam/sdk/core/component/motor/MotorRPCClientTest.kt b/core/sdk/src/test/java/com/viam/sdk/core/component/motor/MotorRPCClientTest.kt new file mode 100644 index 000000000..ef65f8383 --- /dev/null +++ b/core/sdk/src/test/java/com/viam/sdk/core/component/motor/MotorRPCClientTest.kt @@ -0,0 +1,121 @@ +package com.viam.sdk.core.component.motor + +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.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mockito.* +import java.util.* + +class MotorRPCClientTest { + private lateinit var motor: Motor + private lateinit var client: MotorRPCClient + + @JvmField + @Rule + val grpcCleanupRule: GrpcCleanupRule = GrpcCleanupRule() + + @BeforeEach + fun setup() { + motor = mock( + Motor::class.java, withSettings().useConstructor("mock-motor").defaultAnswer( + CALLS_REAL_METHODS + ) + ) + val resourceManager = ResourceManager(listOf(motor)) + val service = MotorRPCService(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 = MotorRPCClient("mock-motor", BasicManagedChannel(channel)) + } + + @Test + fun setPower() { + val extra = + Struct.newBuilder().putAllFields(mapOf("foo" to Value.newBuilder().setStringValue("bar").build())).build() + client.setPower(1.82, extra) + verify(motor).setPower(1.82, extra) + } + + @Test + fun goTo() { + client.goTo(1.23, 2.34) + verify(motor).goTo(1.23, 2.34, Struct.getDefaultInstance()) + } + + @Test + fun resetZeroPosition() { + client.resetZeroPosition(44.0) + verify(motor).resetZeroPosition(44.0, Struct.getDefaultInstance()) + } + + @Test + fun getPosition() { + `when`(motor.getPosition(any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn(78.6) + val pos = client.getPosition() + verify(motor).getPosition(Struct.getDefaultInstance()) + assertEquals(78.6, pos) + } + + @Test + fun getProperties() { + `when`(motor.getProperties(any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn( + Motor.Properties( + positionReporting = true + ) + ) + val properties = client.getProperties() + verify(motor).getProperties(Struct.getDefaultInstance()) + assertTrue(properties.positionReporting) + } + + @Test + fun stop() { + client.stop() + verify(motor).stop(Struct.getDefaultInstance()) + } + + @Test + fun isPowered() { + `when`(motor.isPowered(any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn(Pair(true, 10.1)) + val isPowered = client.isPowered() + verify(motor).isPowered(Struct.getDefaultInstance()) + assertTrue(isPowered.first) + assertEquals(10.1, isPowered.second) + } + + @Test + fun isMoving() { + `when`(motor.isMoving()).thenReturn(false) + val isMoving = client.isMoving() + verify(motor).isMoving() + assertFalse(isMoving) + } + + @Test + fun doCommand() { + val command = mapOf("foo" to Value.newBuilder().setStringValue("bar").build()) + doReturn(Struct.newBuilder().putAllFields(command).build()).`when`(motor).doCommand(anyMap()) + val response = client.doCommand(command) + verify(motor).doCommand(command) + assertEquals(command, response.fieldsMap) + } + + @Test + fun getGeometries() { + doReturn(listOf()).`when`(motor).getGeometries(any()) + client.getGeometries(Optional.empty()) + verify(motor).getGeometries(any()) + } +} diff --git a/core/sdk/src/test/java/com/viam/sdk/core/component/motor/MotorRPCServiceTest.kt b/core/sdk/src/test/java/com/viam/sdk/core/component/motor/MotorRPCServiceTest.kt new file mode 100644 index 000000000..663afd42b --- /dev/null +++ b/core/sdk/src/test/java/com/viam/sdk/core/component/motor/MotorRPCServiceTest.kt @@ -0,0 +1,146 @@ +package com.viam.sdk.core.component.motor + +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.motor.v1.Motor.* +import com.viam.component.motor.v1.MotorServiceGrpc +import com.viam.component.motor.v1.MotorServiceGrpc.MotorServiceBlockingStub +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.* +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockito.Mockito.* +import java.util.* + +class MotorRPCServiceTest { + private lateinit var motor: Motor + private lateinit var client: MotorServiceBlockingStub + + @JvmField + @Rule + val grpcCleanupRule: GrpcCleanupRule = GrpcCleanupRule() + + @BeforeEach + fun setup() { + motor = mock( + Motor::class.java, withSettings().useConstructor("mock-motor").defaultAnswer( + CALLS_REAL_METHODS + ) + ) + + val resourceManager = ResourceManager(listOf(motor)) + val service = MotorRPCService(resourceManager) + val serviceName = InProcessServerBuilder.generateName() + grpcCleanupRule.register( + InProcessServerBuilder.forName(serviceName).directExecutor().addService(service).build().start() + ) + client = MotorServiceGrpc.newBlockingStub( + grpcCleanupRule.register( + InProcessChannelBuilder.forName(serviceName).build() + ) + ) + } + + @Test + fun setPower() { + val extra = + Struct.newBuilder().putAllFields(mapOf("foo" to Value.newBuilder().setStringValue("bar").build())).build() + val request = SetPowerRequest.newBuilder().setName(motor.name.name).setPowerPct(1.82).setExtra(extra).build() + client.setPower(request) + verify(motor).setPower(1.82, extra) + } + + @Test + fun goFor() { + val request = GoForRequest.newBuilder().setName(motor.name.name).setRpm(1.23).setRevolutions(2.34).build() + client.goFor(request) + verify(motor).goFor(1.23, 2.34, Struct.getDefaultInstance()) + } + + @Test + fun goTo() { + val request = + GoToRequest.newBuilder().setName(motor.name.name).setRpm(1.01).setPositionRevolutions(2.02).build() + client.goTo(request) + verify(motor).goTo(1.01, 2.02, Struct.getDefaultInstance()) + } + + @Test + fun resetZeroPosition() { + val request = ResetZeroPositionRequest.newBuilder().setName(motor.name.name).setOffset(2.22).build() + client.resetZeroPosition(request) + verify(motor).resetZeroPosition(2.22, Struct.getDefaultInstance()) + } + + @Test + fun getPosition() { + `when`(motor.getPosition(any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn(78.6) + val request = GetPositionRequest.newBuilder().setName(motor.name.name).build() + val response = client.getPosition(request) + verify(motor).getPosition(Struct.getDefaultInstance()) + assertEquals(78.6, response.position) + } + + @Test + fun getProperties() { + `when`(motor.getProperties(any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn( + Motor.Properties( + true + ) + ) + val request = GetPropertiesRequest.newBuilder().setName(motor.name.name).build() + val response = client.getProperties(request) + verify(motor).getProperties(Struct.getDefaultInstance()) + assertTrue(response.positionReporting) + } + + @Test + fun stop() { + val request = StopRequest.newBuilder().setName(motor.name.name).build() + client.stop(request) + verify(motor).stop(Struct.getDefaultInstance()) + } + + @Test + fun isPowered() { + `when`(motor.isPowered(any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn(Pair(true, 10.1)) + val request = IsPoweredRequest.newBuilder().setName(motor.name.name).build() + val response = client.isPowered(request) + verify(motor).isPowered(Struct.getDefaultInstance()) + assertTrue(response.isOn) + assertEquals(10.1, response.powerPct) + } + + @Test + fun isMoving() { + `when`(motor.isMoving()).thenReturn(false) + val request = IsMovingRequest.newBuilder().setName(motor.name.name).build() + val response = client.isMoving(request) + assertFalse(response.isMoving) + } + + @Test + fun doCommand() { + val command = + Struct.newBuilder().putAllFields(mapOf("foo" to Value.newBuilder().setStringValue("bar").build())).build() + doReturn(command).`when`(motor).doCommand(anyMap()) + val request = Common.DoCommandRequest.newBuilder().setName(motor.name.name).setCommand(command).build() + val response = client.doCommand(request) + verify(motor).doCommand(command.fieldsMap) + assertEquals(command, response.result) + } + + @Test + fun getGeometries() { + doReturn(listOf()).`when`(motor).getGeometries(any()) + val request = Common.GetGeometriesRequest.newBuilder().setName(motor.name.name).build() + client.getGeometries(request) + verify(motor).getGeometries(Optional.of(Struct.getDefaultInstance())) + } +} diff --git a/core/sdk/src/test/java/com/viam/sdk/core/component/motor/MotorTest.kt b/core/sdk/src/test/java/com/viam/sdk/core/component/motor/MotorTest.kt new file mode 100644 index 000000000..d1275af79 --- /dev/null +++ b/core/sdk/src/test/java/com/viam/sdk/core/component/motor/MotorTest.kt @@ -0,0 +1,81 @@ +package com.viam.sdk.core.component.motor + +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 MotorTest { + private lateinit var motor: Motor + + @BeforeEach + fun setup() { + motor = mock(Motor::class.java, Answers.CALLS_REAL_METHODS) + } + + @Test + fun setPower() { + val extra = + Struct.newBuilder().putAllFields(mapOf("foo" to Value.newBuilder().setStringValue("bar").build())).build() + motor.setPower(1.82, extra) + verify(motor).setPower(1.82, extra) + } + + @Test + fun goTo() { + motor.goTo(1.23, 2.34) + verify(motor).goTo(1.23, 2.34) + } + + @Test + fun resetZeroPosition() { + motor.resetZeroPosition(44.0) + verify(motor).resetZeroPosition(44.0) + } + + @Test + fun getPosition() { + `when`(motor.getPosition(any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn(78.6) + val pos = motor.getPosition() + verify(motor).getPosition() + assertEquals(78.6, pos) + } + + @Test + fun getProperties() { + `when`(motor.getProperties(any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn( + Motor.Properties( + positionReporting = true + ) + ) + val properties = motor.getProperties() + verify(motor).getProperties() + assertTrue(properties.positionReporting) + } + + @Test + fun stop() { + motor.stop() + verify(motor).stop() + } + + @Test + fun isPowered() { + `when`(motor.isPowered(any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn(Pair(true, 10.1)) + val isPowered = motor.isPowered() + verify(motor).isPowered() + assertTrue(isPowered.first) + assertEquals(10.1, isPowered.second) + } + + @Test + fun isMoving() { + `when`(motor.isMoving()).thenReturn(false) + val isMoving = motor.isMoving() + verify(motor).isMoving() + assertFalse(isMoving) + } +}