Skip to content

Commit

Permalink
encoder + tests
Browse files Browse the repository at this point in the history
  • Loading branch information
gloriacai01 committed Sep 4, 2024
1 parent 875f9b6 commit cd11f8e
Show file tree
Hide file tree
Showing 7 changed files with 515 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.viam.common.v1.Common.ResourceName;
import com.viam.component.board.v1.BoardServiceGrpc;
import com.viam.component.camera.v1.CameraServiceGrpc;
import com.viam.component.encoder.v1.EncoderServiceGrpc;
import com.viam.component.generic.v1.GenericServiceGrpc;
import com.viam.component.gripper.v1.GripperServiceGrpc;
import com.viam.component.motor.v1.MotorServiceGrpc;
Expand All @@ -15,6 +16,7 @@
import com.viam.sdk.core.component.camera.Camera;
import com.viam.sdk.core.component.camera.CameraRPCClient;
import com.viam.sdk.core.component.camera.CameraRPCService;
import com.viam.sdk.core.component.encoder.*;
import com.viam.sdk.core.component.generic.Generic;
import com.viam.sdk.core.component.generic.GenericRPCClient;
import com.viam.sdk.core.component.generic.GenericRPCService;
Expand Down Expand Up @@ -95,6 +97,12 @@ public class ResourceManager implements Closeable {
SensorRPCService::new,
SensorRPCClient::new
));
Registry.registerSubtype(new ResourceRegistration<>(
Encoder.SUBTYPE,
EncoderServiceGrpc.SERVICE_NAME,
EncoderRPCService::new,
EncoderRPCClient::new
));

// SERVICES
Registry.registerSubtype(new ResourceRegistration<>(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package com.viam.sdk.core.component.encoder

import com.google.protobuf.Struct
import com.viam.component.encoder.v1.Encoder.*
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
typealias Properties = GetPropertiesResponse


/**
* An [Encoder] represents a physical encoder.
*/
abstract class Encoder(name: String) : Component(SUBTYPE, named(name)) {

companion object {
@JvmField
val SUBTYPE = Subtype(Subtype.NAMESPACE_RDK, Subtype.RESOURCE_TYPE_COMPONENT, "encoder")

/**
* 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): Encoder {
return robot.getResource(Encoder::class.java, named(name))
}
}

/**
* Set the current position to be the new zero (home) position.
*/
abstract fun resetPosition(extra: Struct)

/**
* Set the current position to be the new zero (home) position.
*/
fun resetPosition(){
resetPosition(Struct.getDefaultInstance())
}

/**
* Report the position of the encoder.
* The value returned is the current position in terms of it's ``position_type``.
* If no position type is given, the supported position type will be returned.
* The position will be either in relative units (ticks away from a zero position) for
* ``PositionType.POSITION_TYPE_TICKS_COUNT`` or absolute units (degrees along a circle)
* for ``PositionType.POSITION_TYPE_ANGLE_DEGREES``.
* @param positionType The desired output type of the position.
* @return A tuple containing two values; the first [0] the position of the encoder which can either be
* ticks since last zeroing for a relative encoder or degrees for an absolute encoder, and the second [1] the type of
* position the encoder returns (ticks or degrees).
*
*/
abstract fun getPosition(positionType: PositionType?, extra: Struct): Pair<Float, PositionType>

/**
* Report the position of the encoder.
* The value returned is the current position in terms of it's ``position_type``.
* If no position type is given, the supported position type will be returned.
* The position will be either in relative units (ticks away from a zero position) for
* ``PositionType.POSITION_TYPE_TICKS_COUNT`` or absolute units (degrees along a circle)
* for ``PositionType.POSITION_TYPE_ANGLE_DEGREES``.
* @return A tuple containing two values; the first [0] the position of the encoder which can either be
* ticks since last zeroing for a relative encoder or degrees for an absolute encoder, and the second [1] the type of
* position the encoder returns (ticks or degrees).
*/
fun getPosition(positionType: PositionType): Pair<Float, PositionType>{
return getPosition(positionType, Struct.getDefaultInstance())
}

/**
* Report the position of the encoder.
* The value returned is the current position in terms of it's ``position_type``.
* If no position type is given, the supported position type will be returned.
* The position will be either in relative units (ticks away from a zero position) for
* ``PositionType.POSITION_TYPE_TICKS_COUNT`` or absolute units (degrees along a circle)
* for ``PositionType.POSITION_TYPE_ANGLE_DEGREES``.
* @return A tuple containing two values; the first [0] the position of the encoder which can either be
* ticks since last zeroing for a relative encoder or degrees for an absolute encoder, and the second [1] the type of
* position the encoder returns (ticks or degrees).
*
*/
fun getPosition( extra: Struct): Pair<Float, PositionType>{
return getPosition( null, extra)
}

/**
* Report the position of the encoder.
* The value returned is the current position in terms of it's ``position_type``.
* If no position type is given, the supported position type will be returned.
* The position will be either in relative units (ticks away from a zero position) for
* ``PositionType.POSITION_TYPE_TICKS_COUNT`` or absolute units (degrees along a circle)
* for ``PositionType.POSITION_TYPE_ANGLE_DEGREES``.
* @return A tuple containing two values; the first [0] the position of the encoder which can either be
* ticks since last zeroing for a relative encoder or degrees for an absolute encoder, and the second [1] the type of
* position the encoder returns (ticks or degrees).
*/
fun getPosition(): Pair<Float, PositionType>{
return getPosition(null, Struct.getDefaultInstance())
}





/**
* Return a dictionary of the types of position reporting this encoder supports.
* @return a map of position types to supported status.
*/
abstract fun getProperties(extra: Struct): Properties

/**
* Return a dictionary of the types of position reporting this encoder supports.
* @return a map of position types to supported status.
*/
fun getProperties(): Properties {
return getProperties( Struct.getDefaultInstance())
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.viam.sdk.core.component.encoder

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.encoder.v1.EncoderServiceGrpc
import com.viam.component.encoder.v1.EncoderServiceGrpc.EncoderServiceBlockingStub
import com.viam.component.encoder.v1.Encoder.*
import com.viam.sdk.core.rpc.Channel
import java.util.*
import kotlin.jvm.optionals.getOrDefault

class EncoderRPCClient(name: String, channel: Channel) : Encoder(name) {
private val client: EncoderServiceBlockingStub

init {
val client = EncoderServiceGrpc.newBlockingStub(channel)
if (channel.callCredentials.isPresent) {
this.client = client.withCallCredentials(channel.callCredentials.get())
} else {
this.client = client
}
}

override fun resetPosition(extra: Struct) {
val request = ResetPositionRequest.newBuilder().setName(this.name.name).setExtra(extra).build()
this.client.resetPosition(request)
}

override fun getPosition(positionType: PositionType?, extra: Struct): Pair<Float, PositionType> {
val request = GetPositionRequest.newBuilder().setName(this.name.name).setExtra(extra)
if(positionType!=null) {request.setPositionType(positionType)}
val response = this.client.getPosition(request.build())
return (response.value to response.positionType)

}

override fun getProperties(extra: Struct): Properties {
val request = GetPropertiesRequest.newBuilder().setName(this.name.name).setExtra(extra).build()
return this.client.getProperties(request)
}

override fun doCommand(command: Map<String, Value>?): 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<Struct>): List<Common.Geometry> {
val request = GetGeometriesRequest.newBuilder().setName(this.name.name)
.setExtra(extra.getOrDefault(Struct.getDefaultInstance())).build()
val response = this.client.getGeometries(request)
return response.geometriesList
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.viam.sdk.core.component.encoder

import com.viam.common.v1.Common.*
import com.viam.component.encoder.v1.Encoder.*
import com.viam.component.encoder.v1.EncoderServiceGrpc
import com.viam.sdk.core.component.encoder.Encoder
import com.viam.sdk.core.resource.ResourceManager
import com.viam.sdk.core.resource.ResourceRPCService
import io.grpc.stub.StreamObserver
import java.util.*

internal class EncoderRPCService(private val manager: ResourceManager) : EncoderServiceGrpc.EncoderServiceImplBase(),
ResourceRPCService<Encoder> {

override fun resetPosition(
request: ResetPositionRequest,
responseObserver: StreamObserver<ResetPositionResponse>
) {
val encoder = getResource(Encoder.named(request.name))
encoder.resetPosition(request.extra)
responseObserver.onNext(ResetPositionResponse.newBuilder().build())
responseObserver.onCompleted()
}

override fun getPosition(request: GetPositionRequest, responseObserver: StreamObserver<GetPositionResponse>) {
val encoder = getResource(Encoder.named(request.name))
val position: Pair<Float, PositionType> = if(request.hasPositionType())
encoder.getPosition(request.positionType, request.extra)
else
encoder.getPosition(request.extra)
responseObserver.onNext(GetPositionResponse.newBuilder().setPositionType(position.second).setValue(position.first).build())
responseObserver.onCompleted()
}

override fun getProperties(
request: GetPropertiesRequest,
responseObserver: StreamObserver<GetPropertiesResponse>
) {
val encoder = getResource(Encoder.named(request.name))
val properties = encoder.getProperties(request.extra)
responseObserver.onNext(GetPropertiesResponse.newBuilder().setTicksCountSupported(properties.ticksCountSupported).setAngleDegreesSupported(properties.angleDegreesSupported).build())
responseObserver.onCompleted()
}

override fun doCommand(
request: DoCommandRequest, responseObserver: StreamObserver<DoCommandResponse>
) {
val encoder = getResource(Encoder.named(request.name))
val result = encoder.doCommand(request.command.fieldsMap)
responseObserver.onNext(DoCommandResponse.newBuilder().setResult(result).build())
responseObserver.onCompleted()
}

override fun getGeometries(
request: GetGeometriesRequest, responseObserver: StreamObserver<GetGeometriesResponse>
) {
val encoder = getResource(Encoder.named(request.name))
val result = encoder.getGeometries(Optional.of(request.extra))
responseObserver.onNext(GetGeometriesResponse.newBuilder().addAllGeometries(result).build())
responseObserver.onCompleted()
}

override fun getResourceClass(): Class<Encoder> {
return Encoder::class.java
}

override fun getManager(): ResourceManager {
return this.manager
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.viam.sdk.core.component.encoder

import com.google.protobuf.Struct
import com.google.protobuf.Value
import com.viam.common.v1.Common.Geometry
import com.viam.component.encoder.v1.Encoder.*
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.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito.*
import java.util.*

class EncoderRPCClientTest {
private lateinit var encoder: Encoder
private lateinit var client: EncoderRPCClient

@JvmField
@Rule
val grpcCleanupRule: GrpcCleanupRule = GrpcCleanupRule()

@BeforeEach
fun setup() {
encoder = mock(
Encoder::class.java, withSettings().useConstructor("mock-encoder").defaultAnswer(
CALLS_REAL_METHODS
)
)
val resourceManager = ResourceManager(listOf(encoder))
val service = EncoderRPCService(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 = EncoderRPCClient("mock-encoder", BasicManagedChannel(channel))
}

@Test
fun getPosition(){
`when`(encoder.getPosition(eq(null),any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn(23.0f to PositionType.POSITION_TYPE_TICKS_COUNT)
var pos = client.getPosition()
verify(encoder).getPosition(Struct.getDefaultInstance())
assertEquals(23.0f, pos.first)
assertEquals(PositionType.POSITION_TYPE_TICKS_COUNT, pos.second)

`when`(encoder.getPosition(eq(PositionType.POSITION_TYPE_ANGLE_DEGREES),any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn(23.0f to PositionType.POSITION_TYPE_ANGLE_DEGREES)
pos = client.getPosition(PositionType.POSITION_TYPE_ANGLE_DEGREES)
verify(encoder).getPosition(PositionType.POSITION_TYPE_ANGLE_DEGREES, Struct.getDefaultInstance())
assertEquals(23.0f, pos.first)
assertEquals(PositionType.POSITION_TYPE_ANGLE_DEGREES, pos.second)
}

@Test
fun resetPosition(){
client.resetPosition()
verify(encoder).resetPosition(Struct.getDefaultInstance())
}

@Test
fun getProperties(){
`when`(encoder.getProperties(any(Struct::class.java) ?: Struct.getDefaultInstance())).thenReturn(
GetPropertiesResponse.newBuilder().setAngleDegreesSupported(true).setTicksCountSupported(true).build())
val properties = client.getProperties()
verify(encoder).getProperties(Struct.getDefaultInstance())
assertTrue(properties.ticksCountSupported)
assertTrue(properties.angleDegreesSupported)
}
@Test
fun doCommand() {
val command = mapOf("foo" to Value.newBuilder().setStringValue("bar").build())
doReturn(Struct.newBuilder().putAllFields(command).build()).`when`(encoder).doCommand(anyMap())
val response = client.doCommand(command)
verify(encoder).doCommand(command)
assertEquals(command, response.fieldsMap)
}

@Test
fun getGeometries() {
doReturn(listOf<Geometry>()).`when`(encoder).getGeometries(any())
client.getGeometries(Optional.empty())
verify(encoder).getGeometries(any())
}
}
Loading

0 comments on commit cd11f8e

Please sign in to comment.