diff --git a/core/sdk/src/main/java/com/viam/sdk/core/component/powersensor/PowerSensor.java b/core/sdk/src/main/java/com/viam/sdk/core/component/powersensor/PowerSensor.java new file mode 100644 index 000000000..30b1edeab --- /dev/null +++ b/core/sdk/src/main/java/com/viam/sdk/core/component/powersensor/PowerSensor.java @@ -0,0 +1,84 @@ +package com.viam.sdk.core.component.powersensor; + +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; + +import java.util.Map.Entry; + + +import java.util.Map; +import java.util.Optional; + +/** + * PowerSensor reports information about voltage, current and power. + */ +public abstract class PowerSensor extends Component { + public static final Subtype SUBTYPE = new Subtype( + Subtype.NAMESPACE_RDK, + Subtype.RESOURCE_TYPE_COMPONENT, + "power_sensor"); + + public PowerSensor(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 PowerSensor fromRobot(final RobotClient robot, final String name) { + return robot.getResource(PowerSensor.class, named(name)); + } + + /** + * Return the voltage reading of a specified device and whether it is AC or DC. + * + * @return the pair where the first double representing the voltage reading in V, the second bool indicating whether the voltage is AC (`true`) or DC + * (`false`). + */ + public abstract Entry getVoltage(Optional extra); + + /** + * Return the current of a specified device and whether it is AC or DC. + * + * @return the pair where the first double representing the current reading in V, the second bool indicating whether the current is AC (`true`) or DC + * * (`false`). + */ + public abstract Entry getCurrent(Optional extra); + + /** + * Return the power reading in watts. + * + * @return the power reading in watts + */ + public abstract double getPower(Optional extra); + + + /** + * Get the measurements or readings that this power sensor provides. If a sensor is not configured correctly or fails to read a + * piece of data, it will not appear in the readings dictionary. + * + * @return The readings for the PowerSensor. Object should be of type Vector3, GeoPoint, Orientation, or any Value type. Includes voltage in volts (float), current in + * amperes (float), is_ac (bool), and power in watts (float). + */ + public abstract Map getReadings(Optional extra); + + +} \ No newline at end of file diff --git a/core/sdk/src/main/java/com/viam/sdk/core/component/powersensor/PowerSensorRPCClient.java b/core/sdk/src/main/java/com/viam/sdk/core/component/powersensor/PowerSensorRPCClient.java new file mode 100644 index 000000000..3d51fd6a7 --- /dev/null +++ b/core/sdk/src/main/java/com/viam/sdk/core/component/powersensor/PowerSensorRPCClient.java @@ -0,0 +1,66 @@ +package com.viam.sdk.core.component.powersensor; + +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import com.viam.common.v1.Common; +import com.viam.component.powersensor.v1.PowerSensorServiceGrpc; +import com.viam.component.powersensor.v1.Powersensor; +import com.viam.sdk.core.rpc.Channel; +import com.viam.sdk.core.util.Utils; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; + +public class PowerSensorRPCClient extends PowerSensor { + private final PowerSensorServiceGrpc.PowerSensorServiceBlockingStub client; + + public PowerSensorRPCClient(final String name, final Channel chan) { + super(name); + final PowerSensorServiceGrpc.PowerSensorServiceBlockingStub client = PowerSensorServiceGrpc.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 Entry getVoltage(Optional extra) { + Powersensor.GetVoltageRequest.Builder builder = com.viam.component.powersensor.v1.Powersensor.GetVoltageRequest.newBuilder().setName(this.getName().getName()); + extra.ifPresent(builder::setExtra); + Powersensor.GetVoltageResponse response = client.getVoltage(builder.build()); + return Map.entry(response.getVolts(), response.getIsAc()); + } + + @Override + public Map.Entry getCurrent(Optional extra) { + Powersensor.GetCurrentRequest.Builder builder = com.viam.component.powersensor.v1.Powersensor.GetCurrentRequest.newBuilder().setName(this.getName().getName()); + extra.ifPresent(builder::setExtra); + Powersensor.GetCurrentResponse response = client.getCurrent(builder.build()); + return Map.entry(response.getAmperes(), response.getIsAc()); + } + + @Override + public double getPower(Optional extra) { + Powersensor.GetPowerRequest.Builder builder = com.viam.component.powersensor.v1.Powersensor.GetPowerRequest.newBuilder().setName(this.getName().getName()); + extra.ifPresent(builder::setExtra); + return client.getPower(builder.build()).getWatts(); + } + + @Override + public Map getReadings(Optional extra) { + Common.GetReadingsRequest.Builder builder = Common.GetReadingsRequest.newBuilder().setName(this.getName().getName()); + extra.ifPresent(builder::setExtra); + return Utils.sensorReadingsValueToNative(client.getReadings(builder.build()).getReadingsMap()); + + } +} diff --git a/core/sdk/src/main/java/com/viam/sdk/core/component/powersensor/PowerSensorRPCService.java b/core/sdk/src/main/java/com/viam/sdk/core/component/powersensor/PowerSensorRPCService.java new file mode 100644 index 000000000..abb506ec8 --- /dev/null +++ b/core/sdk/src/main/java/com/viam/sdk/core/component/powersensor/PowerSensorRPCService.java @@ -0,0 +1,78 @@ +package com.viam.sdk.core.component.powersensor; + +import com.google.protobuf.Struct; +import com.google.protobuf.Value; +import com.viam.common.v1.Common; +import com.viam.component.powersensor.v1.PowerSensorServiceGrpc; +import com.viam.component.powersensor.v1.Powersensor; +import com.viam.sdk.core.resource.ResourceManager; +import com.viam.sdk.core.resource.ResourceRPCService; +import com.viam.sdk.core.util.Utils; +import io.grpc.stub.StreamObserver; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; + +public class PowerSensorRPCService extends PowerSensorServiceGrpc.PowerSensorServiceImplBase implements ResourceRPCService { + + private final ResourceManager manager; + + public PowerSensorRPCService(ResourceManager manager) { + this.manager = manager; + } + + @Override + public Class getResourceClass() { + return PowerSensor.class; + } + + @Override + public ResourceManager getManager() { + return manager; + } + + + @Override + public void getVoltage(Powersensor.GetVoltageRequest request, StreamObserver responseObserver) { + PowerSensor powerSensor = getResource(PowerSensor.named(request.getName())); + Entry voltage = powerSensor.getVoltage(Optional.of(request.getExtra())); + responseObserver.onNext(com.viam.component.powersensor.v1.Powersensor.GetVoltageResponse.newBuilder().setVolts(voltage.getKey()).setIsAc(voltage.getValue()).build()); + responseObserver.onCompleted(); + } + + @Override + public void getCurrent(Powersensor.GetCurrentRequest request, StreamObserver responseObserver) { + PowerSensor powerSensor = getResource(PowerSensor.named(request.getName())); + Entry current = powerSensor.getCurrent(Optional.of(request.getExtra())); + responseObserver.onNext(com.viam.component.powersensor.v1.Powersensor.GetCurrentResponse.newBuilder().setAmperes(current.getKey()).setIsAc(current.getValue()).build()); + responseObserver.onCompleted(); + } + + @Override + public void getPower(Powersensor.GetPowerRequest request, StreamObserver responseObserver) { + PowerSensor powerSensor = getResource(PowerSensor.named(request.getName())); + double power = powerSensor.getPower(Optional.of(request.getExtra())); + responseObserver.onNext(com.viam.component.powersensor.v1.Powersensor.GetPowerResponse.newBuilder().setWatts(power).build()); + responseObserver.onCompleted(); + } + + @Override + public void getReadings(Common.GetReadingsRequest request, StreamObserver responseObserver) { + PowerSensor powerSensor = getResource(PowerSensor.named(request.getName())); + Map readings = Utils.sensorReadingsNativeToValue(powerSensor.getReadings(Optional.of(request.getExtra()))); + responseObserver.onNext(Common.GetReadingsResponse.newBuilder().putAllReadings(readings).build()); + responseObserver.onCompleted(); + } + @Override + public void doCommand(Common.DoCommandRequest request, + StreamObserver responseObserver) { + + PowerSensor powerSensor = getResource(PowerSensor.named(request.getName())); + Struct result = powerSensor.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 f74704fdc..0d6419917 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 @@ -8,6 +8,7 @@ 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.powersensor.v1.PowerSensorServiceGrpc; import com.viam.component.sensor.v1.SensorServiceGrpc; import com.viam.component.servo.v1.ServoServiceGrpc; import com.viam.sdk.core.component.board.Board; @@ -28,6 +29,9 @@ import com.viam.sdk.core.component.movementsensor.MovementSensor; import com.viam.sdk.core.component.movementsensor.MovementSensorRPCClient; import com.viam.sdk.core.component.movementsensor.MovementSensorRPCService; +import com.viam.sdk.core.component.powersensor.PowerSensor; +import com.viam.sdk.core.component.powersensor.PowerSensorRPCClient; +import com.viam.sdk.core.component.powersensor.PowerSensorRPCService; import com.viam.sdk.core.component.sensor.Sensor; import com.viam.sdk.core.component.sensor.SensorRPCClient; import com.viam.sdk.core.component.sensor.SensorRPCService; @@ -99,6 +103,12 @@ public class ResourceManager implements Closeable { SensorRPCService::new, SensorRPCClient::new )); + Registry.registerSubtype(new ResourceRegistration<>( + PowerSensor.SUBTYPE, + PowerSensorServiceGrpc.SERVICE_NAME, + PowerSensorRPCService::new, + PowerSensorRPCClient::new + )); Registry.registerSubtype(new ResourceRegistration<>( Servo.SUBTYPE, ServoServiceGrpc.SERVICE_NAME, diff --git a/core/sdk/src/main/java/com/viam/sdk/core/util/Utils.java b/core/sdk/src/main/java/com/viam/sdk/core/util/Utils.java new file mode 100644 index 000000000..766e6b8af --- /dev/null +++ b/core/sdk/src/main/java/com/viam/sdk/core/util/Utils.java @@ -0,0 +1,131 @@ +package com.viam.sdk.core.util; + +import com.google.protobuf.*; +import com.viam.common.v1.Common; + +import java.util.*; + +public class Utils { + public static Object valueToNative(Value v) { + switch (v.getKindCase()) { + case STRUCT_VALUE: { + Map structMap = v.getStructValue().getFieldsMap(); + if (structMap.get("_type") != null) { + switch (structMap.get("_type").getStringValue()) { + case ("vector3"): { + return Common.Vector3.newBuilder().setX(structMap.get("x").getNumberValue()).setY(structMap.get("y").getNumberValue()).setZ(structMap.get("z").getNumberValue()).build(); + } + case ("geopoint"): { + return Common.GeoPoint.newBuilder().setLatitude(structMap.get("lat").getNumberValue()).setLongitude(structMap.get("lng").getNumberValue()).build(); + } + case ("orientation_vector_degrees"): { + return Common.Orientation.newBuilder().setOX(structMap.get("ox").getNumberValue()).setOY(structMap.get("oy").getNumberValue()).setOZ(structMap.get("oz").getNumberValue()).setTheta(structMap.get("theta").getNumberValue()).build(); + } + } + + } else { + Map nativeMap = new HashMap<>(); + for (Map.Entry entry : structMap.entrySet()) { + nativeMap.put(entry.getKey(), valueToNative(entry.getValue())); + } + return nativeMap; + + } + + + } + case LIST_VALUE: { + List list = new ArrayList<>(); + for (Value val : v.getListValue().getValuesList()) { + list.add(valueToNative(val)); + } + return list; + + } + case BOOL_VALUE: + return v.getBoolValue(); + case NUMBER_VALUE: + return v.getNumberValue(); + case STRING_VALUE: + return v.getStringValue(); + case NULL_VALUE: + return v.getNullValue(); + default: + return v; + } + + + } + + public static Value nativeToValue(Object obj) throws IllegalArgumentException { + if (obj instanceof Common.Vector3) { + Struct struct = Struct.newBuilder() + .putFields("x", Value.newBuilder().setNumberValue(((Common.Vector3) obj).getX()).build()) + .putFields("y", Value.newBuilder().setNumberValue(((Common.Vector3) obj).getY()).build()) + .putFields("z", Value.newBuilder().setNumberValue(((Common.Vector3) obj).getZ()).build()) + .putFields("_type", Value.newBuilder().setStringValue("vector3").build()) + .build(); + return Value.newBuilder().setStructValue(struct).build(); + + } else if (obj instanceof Common.GeoPoint) { + Struct struct = Struct.newBuilder() + .putFields("lat", Value.newBuilder().setNumberValue(((Common.GeoPoint) obj).getLatitude()).build()) + .putFields("lng", Value.newBuilder().setNumberValue(((Common.GeoPoint) obj).getLongitude()).build()) + .putFields("_type", Value.newBuilder().setStringValue("geopoint").build()) + .build(); + return Value.newBuilder().setStructValue(struct).build(); + } else if (obj instanceof Common.Orientation) { + Struct struct = Struct.newBuilder() + .putFields("ox", Value.newBuilder().setNumberValue(((Common.Orientation) obj).getOX()).build()) + .putFields("oy", Value.newBuilder().setNumberValue(((Common.Orientation) obj).getOY()).build()) + .putFields("oz", Value.newBuilder().setNumberValue(((Common.Orientation) obj).getOZ()).build()) + .putFields("theta", Value.newBuilder().setNumberValue(((Common.Orientation) obj).getTheta()).build()) + .putFields("_type", Value.newBuilder().setStringValue("orientation_vector_degrees").build()) + .build(); + return Value.newBuilder().setStructValue(struct).build(); + + } else if (obj instanceof Number) { + return Value.newBuilder().setNumberValue((double) obj).build(); + } else if (obj instanceof String) { + return Value.newBuilder().setStringValue((String) obj).build(); + } else if(obj instanceof byte[]){ + return Value.newBuilder().setStringValue(new String((byte[]) obj)).build(); + }else if (obj instanceof Boolean) { + return Value.newBuilder().setBoolValue((Boolean) obj).build(); + } else if (obj instanceof List) { + ListValue.Builder listBuilder = ListValue.newBuilder(); + for (Object o : (List) obj) { + listBuilder.addValues(nativeToValue(o)); + } + return Value.newBuilder().setListValue(listBuilder.build()).build(); + } else if (obj instanceof Map) { + Struct.Builder structBuilder = Struct.newBuilder(); + for (Map.Entry entry : ((Map) obj).entrySet()) { + if(!(entry.getKey() instanceof String)) throw new IllegalArgumentException(); + else structBuilder.putFields((String) entry.getKey(), nativeToValue(entry.getValue())); + } + return Value.newBuilder().setStructValue(structBuilder.build()).build(); + + } + else throw new IllegalArgumentException(); + } + + public static Map sensorReadingsNativeToValue(Map readings) { + Map valueMap = new HashMap<>(); + for (Map.Entry entry : readings.entrySet()) { + valueMap.put(entry.getKey(), nativeToValue(entry.getValue())); + } + return valueMap; + } + + public static Map sensorReadingsValueToNative(Map readings) { + Map map = new HashMap<>(); + for (Map.Entry entry : readings.entrySet()) { + map.put(entry.getKey(), valueToNative(entry.getValue())); + + } + return map; + } + + +} \ No newline at end of file diff --git a/core/sdk/src/test/kotlin/com/viam/sdk/core/component/powersensor/PowerSensorRPCClientTest.kt b/core/sdk/src/test/kotlin/com/viam/sdk/core/component/powersensor/PowerSensorRPCClientTest.kt new file mode 100644 index 000000000..c42425789 --- /dev/null +++ b/core/sdk/src/test/kotlin/com/viam/sdk/core/component/powersensor/PowerSensorRPCClientTest.kt @@ -0,0 +1,99 @@ +package com.viam.sdk.core.component.powersensor + +import com.google.protobuf.Struct +import com.google.protobuf.Value +import com.viam.common.v1.Common +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.* +import java.util.AbstractMap + +class PowerSensorRPCClientTest { + private lateinit var powerSensor: PowerSensor + private lateinit var client: PowerSensorRPCClient + + val extra = + Struct.newBuilder().putAllFields(mapOf("foo" to Value.newBuilder().setStringValue("bar").build())).build() + + @JvmField + @Rule + val grpcCleanupRule: GrpcCleanupRule = GrpcCleanupRule() + + @BeforeEach + fun setup() { + powerSensor = mock( + PowerSensor::class.java, withSettings().useConstructor("mock-powersensor").defaultAnswer( + CALLS_REAL_METHODS + ) + ) + val resourceManager = ResourceManager(listOf(powerSensor)) + val service = PowerSensorRPCService(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 = PowerSensorRPCClient("mock-powersensor", BasicManagedChannel(channel)) + } + + @Test + fun getVoltage() { + `when`(powerSensor.getVoltage(any>())).thenReturn(AbstractMap.SimpleEntry(3.0, true)) + val response = client.getVoltage(Optional.of(extra)) + verify(powerSensor).getVoltage(Optional.of(extra)) + assertEquals(3.0, response.key) + assertTrue(response.value) + } + + @Test + fun getCurrent() { + `when`(powerSensor.getCurrent(any>())).thenReturn(AbstractMap.SimpleEntry(3.0, true)) + val response = client.getCurrent(Optional.of(extra)) + verify(powerSensor).getCurrent(Optional.of(extra)) + assertEquals(3.0, response.key) + assertTrue(response.value) + + } + + @Test + fun getPower() { + `when`(powerSensor.getPower(any>())).thenReturn(2.1) + val response = client.getPower(Optional.of(extra)) + verify(powerSensor).getPower(Optional.of(extra)) + assertEquals(2.1, response) + } + + @Test + fun getReadings() { + val readings = mapOf( + "a" to 1.0, + "b" to 2.0, + "c" to 3.0, + "d" to mapOf("vec3" to Common.Vector3.newBuilder().setX(1.0).setY(1.0).setZ(1.0).build()) + ) + `when`(powerSensor.getReadings(any>())).thenReturn(readings) + val response = client.getReadings(Optional.of(extra)) + verify(powerSensor).getReadings(Optional.of(extra)) + assertEquals(readings, response) + + } + + @Test + fun doCommand() { + val command = mapOf("abc" to Value.newBuilder().setStringValue("123").build()) + doReturn(Struct.newBuilder().putAllFields(command).build()).`when`(powerSensor).doCommand(anyMap()) + val response = client.doCommand(command) + verify(powerSensor).doCommand(command) + assertEquals(command, response.fieldsMap) + } + +} \ No newline at end of file diff --git a/core/sdk/src/test/kotlin/com/viam/sdk/core/component/powersensor/PowerSensorRPCServiceTest.kt b/core/sdk/src/test/kotlin/com/viam/sdk/core/component/powersensor/PowerSensorRPCServiceTest.kt new file mode 100644 index 000000000..6d1baf77b --- /dev/null +++ b/core/sdk/src/test/kotlin/com/viam/sdk/core/component/powersensor/PowerSensorRPCServiceTest.kt @@ -0,0 +1,117 @@ +package com.viam.sdk.core.component.powersensor +import com.google.protobuf.Struct +import com.google.protobuf.Value +import com.viam.common.v1.Common +import com.viam.component.powersensor.v1.Powersensor.* +import com.viam.component.powersensor.v1.PowerSensorServiceGrpc +import com.viam.component.powersensor.v1.PowerSensorServiceGrpc.PowerSensorServiceBlockingStub +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 PowerSensorRPCServiceTest { + private lateinit var powerSensor: PowerSensor + private lateinit var client: PowerSensorServiceBlockingStub + + val extra = + Struct.newBuilder().putAllFields(mapOf("foo" to Value.newBuilder().setStringValue("bar").build())).build() + + @JvmField + @Rule + val grpcCleanupRule: GrpcCleanupRule = GrpcCleanupRule() + + @BeforeEach + fun setup() { + powerSensor = mock( + PowerSensor::class.java, withSettings().useConstructor("mock-powersensor").defaultAnswer( + CALLS_REAL_METHODS + ) + ) + + val resourceManager = ResourceManager(listOf(powerSensor)) + val service = PowerSensorRPCService(resourceManager) + val serviceName = InProcessServerBuilder.generateName() + grpcCleanupRule.register( + InProcessServerBuilder.forName(serviceName).directExecutor().addService(service).build().start() + ) + client = PowerSensorServiceGrpc.newBlockingStub( + grpcCleanupRule.register( + InProcessChannelBuilder.forName(serviceName).build() + ) + ) + } + @Test + fun getVoltage(){ + `when`(powerSensor.getVoltage(any>())).thenReturn(AbstractMap.SimpleEntry(3.0, true)) + val request = GetVoltageRequest.newBuilder().setName(powerSensor.name.name).setExtra(extra).build() + val response = client.getVoltage(request) + verify(powerSensor).getVoltage(Optional.of(extra)) + assertEquals(3.0, response.volts) + assertTrue(response.isAc) + + } + @Test + fun getCurrent(){ + `when`(powerSensor.getCurrent(any>())).thenReturn(AbstractMap.SimpleEntry(3.0, true)) + val request = GetCurrentRequest.newBuilder().setName(powerSensor.name.name).setExtra(extra).build() + val response = client.getCurrent(request) + verify(powerSensor).getCurrent(Optional.of(extra)) + assertEquals(3.0, response.amperes) + assertTrue(response.isAc) + } + @Test + fun getPower(){ + `when`(powerSensor.getPower(any>())).thenReturn(2.0) + val request = GetPowerRequest.newBuilder().setName(powerSensor.name.name).setExtra(extra).build() + val response = client.getPower(request) + verify(powerSensor).getPower(Optional.of(extra)) + assertEquals(2.0, response.watts) + + } + @Test + fun getReadings(){ + val readings = mapOf( + "a" to 1.0, + "b" to 2.0, + "c" to 3.0, + "d" to mapOf("vec3" to Common.Vector3.newBuilder().setX(1.0).setY(1.0).setZ(1.0).build()) + ) + val struct = Struct.newBuilder().apply{ + putFields("x", Value.newBuilder().setNumberValue(1.0).build()) + putFields("y", Value.newBuilder().setNumberValue(1.0).build()) + putFields("z", Value.newBuilder().setNumberValue(1.0).build()) + putFields("_type", Value.newBuilder().setStringValue("vector3").build()) + }.build() + + val valueReadings = mapOf("a" to Value.newBuilder().setNumberValue(1.0).build(), + "b" to Value.newBuilder().setNumberValue(2.0).build(), + "c" to Value.newBuilder().setNumberValue(3.0).build(), + "d" to Value.newBuilder().setStructValue(Struct.newBuilder().putAllFields(mapOf("vec3" to Value.newBuilder().setStructValue(struct).build()))).build()) + `when`(powerSensor.getReadings(any>())).thenReturn(readings) + val request = Common.GetReadingsRequest.newBuilder().setName(powerSensor.name.name).setExtra(extra).build() + val response = client.getReadings(request) + verify(powerSensor).getReadings(Optional.of(extra)) + assertEquals(valueReadings, response.readingsMap) + + + } + + @Test + fun doCommand() { + val command = + Struct.newBuilder().putAllFields(mapOf("foo" to Value.newBuilder().setStringValue("bar").build())).build() + doReturn(command).`when`(powerSensor).doCommand(anyMap()) + val request = Common.DoCommandRequest.newBuilder().setName(powerSensor.name.name).setCommand(command).build() + val response = client.doCommand(request) + verify(powerSensor).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/powersensor/PowerSensorTest.kt b/core/sdk/src/test/kotlin/com/viam/sdk/core/component/powersensor/PowerSensorTest.kt new file mode 100644 index 000000000..7d6a16002 --- /dev/null +++ b/core/sdk/src/test/kotlin/com/viam/sdk/core/component/powersensor/PowerSensorTest.kt @@ -0,0 +1,65 @@ +package com.viam.sdk.core.component.powersensor + +import com.google.protobuf.Struct +import com.google.protobuf.Value +import com.viam.common.v1.Common +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.* +import java.util.* + +class PowerSensorTest { + private lateinit var powerSensor: PowerSensor + val extra = + Struct.newBuilder().putAllFields(mapOf("foo" to Value.newBuilder().setStringValue("bar").build())).build() + + @BeforeEach + fun setup() { + powerSensor = mock(PowerSensor::class.java, Answers.CALLS_REAL_METHODS) + } + + @Test + fun getVoltage(){ + `when`(powerSensor.getVoltage(any>())).thenReturn(AbstractMap.SimpleEntry(3.0, true)) + val voltage = powerSensor.getVoltage(Optional.of(extra)) + verify(powerSensor).getVoltage(Optional.of(extra)) + assertEquals(3.0, voltage.key) + assertTrue(voltage.value) + + } + + @Test + fun getCurrent(){ + `when`(powerSensor.getCurrent(any>())).thenReturn(AbstractMap.SimpleEntry(5.0, false)) + val curr = powerSensor.getCurrent(Optional.of(extra)) + verify(powerSensor).getCurrent(Optional.of(extra)) + assertEquals(5.0, curr.key) + assertFalse(curr.value) + + } + + @Test + fun getPower(){ + `when`(powerSensor.getPower(any>())).thenReturn(4.2) + val power = powerSensor.getPower(Optional.of(Struct.getDefaultInstance())) + verify(powerSensor).getPower(Optional.of(Struct.getDefaultInstance())) + assertEquals(4.2, power) + + } + + @Test + fun getReadings(){ + val readings = mapOf( + "a" to 1, + "b" to 2, + "c" to 3, + "d" to mapOf("d1" to "vec3" to Common.Vector3.newBuilder().setX(1.0).setY(1.0).setZ(1.0).build()) + ) + `when`(powerSensor.getReadings(any>())).thenReturn(readings) + val result = powerSensor.getReadings(Optional.of(extra)) + verify(powerSensor).getReadings(Optional.of(extra)) + assertEquals(readings, result) + } +} \ No newline at end of file diff --git a/core/sdk/src/test/kotlin/com/viam/sdk/core/util/UtilsTest.kt b/core/sdk/src/test/kotlin/com/viam/sdk/core/util/UtilsTest.kt new file mode 100644 index 000000000..d534da824 --- /dev/null +++ b/core/sdk/src/test/kotlin/com/viam/sdk/core/util/UtilsTest.kt @@ -0,0 +1,149 @@ +package com.viam.sdk.core.util + +import com.google.protobuf.* +import com.viam.common.v1.Common +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.BeforeEach + +class UtilsTest { + + private lateinit var simpleMap : Map + private lateinit var vector3 : Common.Vector3 + private lateinit var geoPoint : Common.GeoPoint + private lateinit var orientation : Common.Orientation + private lateinit var nestedStruct : Map + + private lateinit var vector3val : Value + private lateinit var geoPointVal : Value + private lateinit var orientationVal : Value + + @BeforeEach + fun setup(){ + simpleMap = mapOf("foo" to "bar", "baz" to true) + vector3 = Common.Vector3.newBuilder().setX(1.0).setY(2.0).setZ(3.0).build() + geoPoint = Common.GeoPoint.newBuilder().setLatitude(22.0).setLongitude(33.0).build() + orientation = Common.Orientation.newBuilder().setOX(1.0).setOY(1.0).setOZ(1.0).setTheta(1.0).build() + nestedStruct = mapOf( + "vec3" to vector3, + "geoPoint" to geoPoint, + "nest" to mapOf("orientation" to orientation, "foo" to "bar", "baz" to true)) + + vector3val = Value.newBuilder().setStructValue( + Struct.newBuilder().putAllFields(mapOf( + "x" to Value.newBuilder().setNumberValue(1.0).build(), + "y" to Value.newBuilder().setNumberValue(2.0).build(), + "z" to Value.newBuilder().setNumberValue(3.0).build(), + "_type" to Value.newBuilder().setStringValue("vector3").build())) + .build()).build() + geoPointVal = Value.newBuilder().setStructValue( + Struct.newBuilder().putAllFields(mapOf( + "lat" to Value.newBuilder().setNumberValue(22.0).build(), + "lng" to Value.newBuilder().setNumberValue(33.0).build(), + "_type" to Value.newBuilder().setStringValue("geopoint").build())) + .build()).build() + orientationVal = Value.newBuilder().setStructValue( + Struct.newBuilder().putAllFields(mapOf( + "ox" to Value.newBuilder().setNumberValue(1.0).build(), + "oy" to Value.newBuilder().setNumberValue(1.0).build(), + "oz" to Value.newBuilder().setNumberValue(1.0).build(), + "theta" to Value.newBuilder().setNumberValue(1.0).build(), + "_type" to Value.newBuilder().setStringValue("orientation_vector_degrees").build())) + .build()).build() + } + @Test + fun valueToNative(){ + var value = Value.newBuilder().setStringValue("value").build() + assertEquals("value", Utils.valueToNative(value)) + + value = Value.newBuilder().setNumberValue(2.0).build() + assertEquals(2.0, Utils.valueToNative(value)) + + value = Value.newBuilder().setNullValue(NullValue.NULL_VALUE).build() + assertEquals(NullValue.NULL_VALUE, Utils.valueToNative(value)) + + value = Value.newBuilder().setBoolValue(true).build() + assertEquals(true, Utils.valueToNative(value)) + + val struct = Struct.newBuilder() + .putFields("foo", Value.newBuilder().setStringValue("bar").build()) + .putFields("baz", Value.newBuilder().setBoolValue(true).build()) + + value = Value.newBuilder().setStructValue(struct.build()).build() + assertEquals(simpleMap, Utils.valueToNative(value)) + + val list = ListValue.newBuilder() + .addValues(Value.newBuilder().setBoolValue(true).build()) + .addValues(Value.newBuilder().setNumberValue(1.0).build()) + .addValues(Value.newBuilder().setStringValue("two").build()) + .build() + value = Value.newBuilder().setListValue(list).build() + assertEquals(listOf(true, 1.0, "two"), Utils.valueToNative(value)) + + + assertEquals(vector3, Utils.valueToNative(vector3val)) + assertEquals(geoPoint, Utils.valueToNative(geoPointVal)) + assertEquals(orientation, Utils.valueToNative(orientationVal)) + + + struct.putFields("orientation", orientationVal) + val nestedStructVal = Struct.newBuilder().putAllFields(mapOf( + "vec3" to vector3val, + "geoPoint" to geoPointVal, + "nest" to Value.newBuilder().setStructValue(struct.build()).build())).build() + value = Value.newBuilder().setStructValue(nestedStructVal).build() + assertEquals(nestedStruct, Utils.valueToNative(value)) + + } + + @Test + fun nativeToValue(){ + var value = Value.newBuilder().setStringValue("abc").build() + assertEquals(value, Utils.nativeToValue("abc")) + assertEquals(value, Utils.nativeToValue("abc".toByteArray())) + + value = Value.newBuilder().setNumberValue(1.0).build() + assertEquals(value, Utils.nativeToValue(1.0)) + + value = Value.newBuilder().setBoolValue(false).build() + assertEquals(value, Utils.nativeToValue(false)) + + val struct = Struct.newBuilder() + .putFields("foo", Value.newBuilder().setStringValue("bar").build()) + .putFields("baz", Value.newBuilder().setBoolValue(true).build()) + + value = Value.newBuilder().setStructValue(struct.build()).build() + assertEquals(value, Utils.nativeToValue(simpleMap)) + + val list = ListValue.newBuilder().addAllValues( + listOf( + Value.newBuilder().setBoolValue(true).build(), + Value.newBuilder().setNumberValue(1.0).build(), + Value.newBuilder().setStringValue("two").build(), + Value.newBuilder().setStructValue(struct).build() + ) + ) + value = Value.newBuilder().setListValue(list).build() + assertEquals(value, Utils.nativeToValue(listOf(true, 1.0, "two", simpleMap))) + + assertEquals(vector3val, Utils.nativeToValue(vector3)) + assertEquals(geoPointVal, Utils.nativeToValue(geoPoint)) + assertEquals(orientationVal, Utils.nativeToValue(orientation)) + + //only string are allowed as keys + assertThrows(IllegalArgumentException::class.java) {Utils.nativeToValue(mapOf(1 to 2, 3 to 4))} + class UnsupportedType(){} + assertThrows(IllegalArgumentException::class.java) {Utils.nativeToValue(UnsupportedType())} + + + + } + + @Test + fun testSensorReadings(){ + val test = Utils.sensorReadingsNativeToValue(nestedStruct) + val response = Utils.sensorReadingsValueToNative(test) + assertEquals(nestedStruct, response) + + } +} \ No newline at end of file