Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RSDK-8714: Input Controller wrappers #70

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/sdk/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ buildscript {
}
plugins {
id "com.github.hierynomus.license-report" version "0.16.1"
id "org.jetbrains.kotlin.jvm" version "2.0.0"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(minor) probably should get rid of this newline?

}

apply plugin: 'kotlin'

ext.pomDisplayName = "Viam Core SDK"

dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0-RC.2")
api 'io.grpc:grpc-protobuf-lite:1.63.0'
api 'io.grpc:grpc-stub:1.63.0'
implementation 'org.json:json:20240205'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.viam.component.gantry.v1.GantryServiceGrpc;
import com.viam.component.generic.v1.GenericServiceGrpc;
import com.viam.component.gripper.v1.GripperServiceGrpc;
import com.viam.component.inputcontroller.v1.InputControllerServiceGrpc;
import com.viam.component.motor.v1.MotorServiceGrpc;
import com.viam.component.movementsensor.v1.MovementSensorServiceGrpc;
import com.viam.component.powersensor.v1.PowerSensorServiceGrpc;
Expand All @@ -34,6 +35,7 @@
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.input.*;
import com.viam.sdk.core.component.motor.Motor;
import com.viam.sdk.core.component.motor.MotorRPCClient;
import com.viam.sdk.core.component.motor.MotorRPCService;
Expand Down Expand Up @@ -121,6 +123,12 @@ public class ResourceManager implements Closeable {
GripperRPCService::new,
GripperRPCClient::new
));
Registry.registerSubtype(new ResourceRegistration<>(
Controller.SUBTYPE,
InputControllerServiceGrpc.SERVICE_NAME,
InputControllerRPCService::new,
InputControllerRPCClient::new
));
Registry.registerSubtype(new ResourceRegistration<>(
Motor.SUBTYPE,
MotorServiceGrpc.SERVICE_NAME,
Expand Down Expand Up @@ -158,6 +166,7 @@ public class ResourceManager implements Closeable {
ServoRPCClient::new
));


// SERVICES
Registry.registerSubtype(new ResourceRegistration<>(
DataManager.SUBTYPE,
Expand Down
254 changes: 254 additions & 0 deletions core/sdk/src/main/kotlin/com/viam/sdk/core/component/input/Input.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
package com.viam.sdk.core.component.input

import com.google.protobuf.Struct
import com.google.protobuf.Timestamp
import com.viam.common.v1.Common.ResourceName
import com.viam.component.inputcontroller.v1.InputController
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


/**
* EventType represents the type of input event.
*/
enum class EventType(val value: String) {

/** Callbacks registered for this event will be called in ADDITION to other registered event callbacks.*/
ALL_EVENTS("AllEvents"),

/** Sent at controller initialization, and on reconnects. */
CONNECT("Connect"),

/** If unplugged, or wireless/network times out.*/
DISCONNECT("Disconnect"),

/** Typical key press.*/
BUTTON_PRESS("ButtonPress"),

/** Key release */
BUTTON_RELEASE("ButtonRelease"),

/** Key is held down. This wil likely be a repeated event.*/
BUTTON_HOLD("ButtonHold"),

/** Both up and down for convenience during registration, not typically emitted.*/
BUTTON_CHANGE("ButtonChange"),

/** Absolute position is reported via Value, a la joysticks. */
POSITION_CHANGE_ABSOLUTE("PositionChangeAbs"),

/** Relative position is reported via Value, a la mice, or simulating axes with up/down buttons. */
POSITION_CHANGE_RELATIVE("PositionChangeRel");

companion object {
fun fromValue(value: String): EventType = when (value) {
"AllEvents" -> EventType.ALL_EVENTS
"Connect" -> EventType.CONNECT
"Disconnect" -> EventType.DISCONNECT
"ButtonPress" -> EventType.BUTTON_PRESS
"ButtonRelease" -> EventType.BUTTON_RELEASE
"ButtonHold" -> EventType.BUTTON_HOLD
"ButtonChange" -> EventType.BUTTON_CHANGE
"PositionChangeAbs" -> EventType.POSITION_CHANGE_ABSOLUTE
"PositionChangeRel" -> EventType.POSITION_CHANGE_RELATIVE
else -> throw IllegalArgumentException("Unknown event type $value")
}
}
}


/**
* Control identifies the input (specific Axis or Button) of a controller.
*/
enum class Control(val value: String) {

// Axes
ABSOLUTE_X("AbsoluteX"),
ABSOLUTE_Y("AbsoluteY"),
ABSOLUTE_Z("AbsoluteZ"),
ABSOLUTE_RX("AbsoluteRX"),
ABSOLUTE_RY("AbsoluteRY"),
ABSOLUTE_RZ("AbsoluteRZ"),
ABSOLUTE_HAT0_X("AbsoluteHat0X"),
ABSOLUTE_HAT0_Y("AbsoluteHat0Y"),

// Buttons
BUTTON_SOUTH("ButtonSouth"),
BUTTON_EAST("ButtonEast"),
BUTTON_WEST("ButtonWest"),
BUTTON_NORTH("ButtonNorth"),
BUTTON_LT("ButtonLT"),
BUTTON_RT("ButtonRT"),
BUTTON_LT2("ButtonLT2"),
BUTTON_RT2("ButtonRT2"),
BUTTON_L_THUMB("ButtonLThumb"),
BUTTON_R_THUMB("ButtonRThumb"),
BUTTON_SELECT("ButtonSelect"),
BUTTON_START("ButtonStart"),
BUTTON_MENU("ButtonMenu"),
BUTTON_RECORD("ButtonRecord"),
BUTTON_E_STOP("ButtonEStop"),

// Pedals
ABSOLUTE_PEDAL_ACCELERATOR("AbsolutePedalAccelerator"),
ABSOLUTE_PEDAL_BRAKE("AbsolutePedalBrake"),
ABSOLUTE_PEDAL_CLUTCH("AbsolutePedalClutch");

companion object {
fun fromValue(value: String): Control = when (value) {
"AbsoluteX" -> ABSOLUTE_X
"AbsoluteY" -> ABSOLUTE_Y
"AbsoluteZ" -> ABSOLUTE_Z
"AbsoluteRX" -> ABSOLUTE_RX
"AbsoluteRY" -> ABSOLUTE_RY
"AbsoluteRZ" -> ABSOLUTE_RZ
"AbsoluteHat0X" -> ABSOLUTE_HAT0_X
"AbsoluteHat0Y" -> ABSOLUTE_HAT0_Y
"ButtonSouth" -> BUTTON_SOUTH
"ButtonEast" -> BUTTON_EAST
"ButtonWest" -> BUTTON_WEST
"ButtonNorth" -> BUTTON_NORTH
"ButtonLT" -> BUTTON_LT
"ButtonRT" -> BUTTON_RT
"ButtonLT2" -> BUTTON_LT2
"ButtonRT2" -> BUTTON_RT2
"ButtonLThumb" -> BUTTON_L_THUMB
"ButtonRThumb" -> BUTTON_R_THUMB
"ButtonSelect" -> BUTTON_SELECT
"ButtonStart" -> BUTTON_START
"ButtonMenu" -> BUTTON_MENU
"ButtonRecord" -> BUTTON_RECORD
"ButtonEStop" -> BUTTON_E_STOP
"AbsolutePedalAccelerator" -> ABSOLUTE_PEDAL_ACCELERATOR
"AbsolutePedalBrake" -> ABSOLUTE_PEDAL_BRAKE
"AbsolutePedalClutch" -> ABSOLUTE_PEDAL_CLUTCH
else -> {
throw IllegalArgumentException("Unknown control $value")
}
}
}

}

class Event(val time: Long, val event: EventType, val control: Control, val value: Double) {

fun proto(): InputController.Event {
return InputController.Event.newBuilder().setEvent(event.value).setControl(control.value).setValue(value)
.setTime(Timestamp.newBuilder().setSeconds(time).build()).build()
}

companion object {
fun fromProto(proto: InputController.Event): Event {
return Event(
proto.time.seconds,
EventType.fromValue(proto.event),
Control.fromValue(proto.control),
proto.value
)
}
}
}
typealias ControlFunction = (Event) -> Unit

/**
* Controller is a logical "container" more than an actual device
* Could be a single gamepad, or a collection of digitalInterrupts and analogReaders, a keyboard, etc.
*/
abstract class Controller(name: String) : Component(SUBTYPE, named(name)) {
companion object {
@JvmField
val SUBTYPE = Subtype(Subtype.NAMESPACE_RDK, Subtype.RESOURCE_TYPE_COMPONENT, "input_controller")

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

/**
* Returns a list of Controls provided by the Controller
* @return List of controls provided by the Controller
stuqdog marked this conversation as resolved.
Show resolved Hide resolved
*/
abstract fun getControls(extra: Struct): List<Control>

/**
* Returns a list of Controls provided by the Controller
* @return List of controls provided by the Controller
*/
fun getControls(): List<Control> {
return getControls(Struct.getDefaultInstance())
}


/**
* Returns the most recent Event for each input (which should be the current state)
* @return the most recent event for each input
*/
abstract fun getEvents(extra: Struct): Map<Control, Event>

/**
* Returns the most recent Event for each input (which should be the current state)
* @return the most recent event for each input
*/
fun getEvents(): Map<Control, Event> {
return getEvents(Struct.getDefaultInstance());
}

/**
* Register a function that will fire on given EventTypes for a given Control
* @param control the control to register the function for
* @param triggers the events that will trigger the function
* @param function the function to run on specific triggers
*/
abstract fun registerControlCallback(
control: Control,
triggers: List<EventType>,
function: ControlFunction?,
extra: Struct
)

/**
* Register a function that will fire on given EventTypes for a given Control
* @param control the control to register the function for
* @param triggers the events that will trigger the function
* @param function the function to run on specific triggers
*/
fun registerControlCallback(control: Control, triggers: List<EventType>, function: ControlFunction?) {
registerControlCallback(control, triggers, function, Struct.getDefaultInstance())
}

/**
* Directly send an Event (such as a button press) from external code
* @param event the event to trigger
*/
abstract fun triggerEvent(event: Event, extra: Struct)

/**
* Directly send an Event (such as a button press) from external code
* @param event the event to trigger
*/
fun triggerEvent(event: Event) {
triggerEvent(event, Struct.getDefaultInstance())
}


}

Loading