GitHub link to code for those who just want to see that.
When making interactive installations getting hardware input from things like potentiometers, various sensors (like light or temperature) or outputting to something like a LED light, relay or motor can be quite interesting. I’m sure a lot of us has done this with something like Arduino, but personally I’ve found it a bit annoying to work with (tho workable).
Some years ago I discovered Phidgets. The name is a riff on the 90s word for GUI components: ‘widgets’ and ‘physical’. This is a complete system including hardware, software and API for many different languages and operating systems that allows you to interact with sensors, actuators, RFID readers/writers and other hardware.
To me Phidgets seem to be a bit of a hidden gem in the world of getting non-typical I/O to a computer so I’d thought I’d make a short guide on how to use them here. Note: I’m not affiliated with Phidgets, I just like their stuff.
One of the platforms Phidgets support is the JVM and the excellent integration Kotlin has with Java makes it very ergonomic to use and program (especially with OPENRNDR).
Hardware
Obviously, to follow along you need to have some hardware from Phidgets. I recommend getting the VINT Hub and some components like a rotary or linear potentiometer or maybe another kind of analog sensor like a distance sensor or anything else that you find interesting. Don’t forget order cables to connect things together!
Setup and installation
Assuming you have some Phidget hardware, to get started you need to install the drivers. After you’ve installed the drivers connect your Phidget hardware. For our example here I’m using a VINT Hub and a rotary potentiometer but feel free to use something else.
After you’ve connected your Phidget, open the Phidget Control Panel that’s included with the drivers. The Control Panel is a very useful tool to debug your Phidget hardware and figure out how to program it. Important gotcha: Phidget doesn’t allow more than one program connected to a Phidget at the same time, so make sure to close down Phidget Control Panel before your run your own programs!
Depending on what you plug in. It might show up automatically (if it’s a newer VINT component) or you have to manually choose to listen to one the ports.
Assuming we have connected a rotary potentiometer like in the picture we can naviage to it’s Hub Port of 0 and open the Voltage Ratio Input which will give us a value between 0 and 1 depending on the how the knob is rotated.
Double clicking this should give you this window:
Changing the knob should change value of Voltage Ratio to the right. As you can see you here can find information such as Hub Port (and Channel) which you often need to connect to the correct sensor.
Using it in a OPENRNDR
This parts assumes you’ve already set up a OPENRNDR project described in the guide.
Add phidget library to our project
To use Phidgets we need to add and configure the Phidgets JAR to our project. Download the Phidgets zipped from the bottom of this page. Unzip the zip if necessary. Create a folder lib under src in your project and put phidget22.jar there. (Note: if your on mac and get a cannot move error you might need to move the jar to the same disk as your project resides first.)
Now we need to configure gradle to use our phidgets library.
Open build.gradle.kts. Scroll down to the plugins section and add java-library
to the plugins section using backticks like so:
plugins {
java
`java-library`
kotlin("jvm") version("1.4.32")
id("com.github.johnrengelman.shadow") version ("6.1.0")
id("org.beryx.runtime") version ("1.11.4")
}
Scroll further down to dependencies and add *implementation*(files(“$*projectDir*/src/lib/phidget22.jar”))
to it like so:
dependencies {
/* This is where you add additional (third-party) dependencies */
implementation(files("$projectDir/src/lib/phidget22.jar"))
// […]
}
Now you need to load gradle changes. You might also want to build/run your program once as well (this seems to sometimes fix stuff).
Get data from the phidgets
Now we can finally start to code! Remove most of TemplateProgram so it just becomes program with a empty extend block in it. Let’s just start with a very simple program where you read the voltageRatio once.
import com.phidget22.VoltageRatioInput
import org.openrndr.application
fun main() = application {
configure {
width = 768
height = 576
}
program {
val phidgetInput = VoltageRatioInput()
// Configure settings depending on what it connected where
phidgetInput.isHubPortDevice = true
phidgetInput.hubPort = 0
// Use event listening to get value
phidgetInput.addVoltageRatioChangeListener {
val value = it.voltageRatio
println(value)
}
phidgetInput.open()
extend {
}
}
}
Running this should print a value based on what you’re doing with your potentiometer or sensor. Remember to close Phidget Control Panel if you still open before you run it!
Please note that we use a callback in an eventlistener to get data. Phidgets also have a property that you can use, but if you get data from it too often (like say in OPENRNDRs render loop inside extend { …}) you’ll get problems. It’s a good habit to always use the events for getting data.
If we want to display the value graphically we can set a variable in the event and refer to it in the drawing loop like so:
…
// Use event listening to get value
var value = 0.0
phidgetInput.addVoltageRatioChangeListener {
value = it.voltageRatio
}
…
extend {
val x = value * width
drawer.circle(x, 20.0, 5.0)
}
Now the circle will follow the value of your sensor.
We can clean up the code using Kotlin’s built in with
scoping function and avoid the need to store the object:
var value = 0.0
with (VoltageRatioInput()) {
// Configure settings depending on what it connected where
isHubPortDevice = true
hubPort = 0
// Use event to get value
addVoltageRatioChangeListener {
value = it.voltageRatio
}
open()
}
You might notice that the circle doesn’t follow your value smoothly. This is because the dataInterval is set to it’s default of 250ms. To fix this we can set the dataInterval to a lower value, but to do this we must do this inside the onAttach event since phidgets doesn’t allow you to do that if you set it before it’s connected.
addAttachListener {
dataInterval = 25
}
It’s good practice to close your connection when you’re done. Tho if you forget in OPENRNDR it’s probably no big deal as Phidgets automatically close when the program stops, but it’s a good thing to be concious of, espescially if you connect and disconnect from them in the same program. Additionally, on other platforms it’s important to close even if your program stops. We use OPENRNDR ended event to do this:
ended.listen { close() }
Putting it all toghether again we have this:
import com.phidget22.VoltageRatioInput
import org.openrndr.application
fun main() = application {
configure {
width = 768
height = 576
}
program {
var value = 0.0
with (VoltageRatioInput()) {
// Configure settings depending on what it connected where
isHubPortDevice = true
hubPort = 0
// Configuration required to set after attach
addAttachListener {
dataInterval = 25
}
// Use event to get value
addVoltageRatioChangeListener {
value = it.voltageRatio
}
open()
ended.listen { close() }
}
extend {
val x = value * width
drawer.circle(x, 20.0, 5.0)
}
}
}
If you have different analog sensors, trying connecting them instead of the one you have connected here.
The following examples will just be code with some commentary if needed.
Digital Input (for example, push button)
import com.phidget22.DigitalInput
import org.openrndr.application
import org.openrndr.color.ColorRGBa
fun main() = application {
configure {
width = 768
height = 576
}
program {
var state = false
with (DigitalInput()) {
hubPort = 0
addStateChangeListener {
state = it.state
println("DigitalInput state changed to ${it.state}")
}
open()
ended.listen { close() }
}
extend {
if (state) {
drawer.clear(ColorRGBa.GREEN)
} else {
drawer.clear(ColorRGBa.RED)
}
}
}
}
Draw with joystick
Here’s an example where I combine the two (using the Thumbstick Phidget).
import com.phidget22.DigitalInput
import com.phidget22.VoltageRatioInput
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.draw.isolatedWithTarget
import org.openrndr.draw.renderTarget
import org.openrndr.launch
const val MOVEMENT_MULTIPLIER = 15.0
fun main() = application {
configure {
width = 768
height = 576
}
program {
// Use this to draw on
val rtCanvas = renderTarget(width, height) { colorBuffer() }
drawer.isolatedWithTarget(rtCanvas) {
drawer.clear(ColorRGBa.BLACK)
}
var y = drawer.bounds.center.y
with (VoltageRatioInput()) {
hubPort = 0
channel = 0
addAttachListener {
dataInterval = 25
}
addVoltageRatioChangeListener {
y += -it.voltageRatio * MOVEMENT_MULTIPLIER
}
open()
ended.listen { close() }
}
var x = drawer.bounds.center.x
with (VoltageRatioInput()) {
hubPort = 0
channel = 1
addVoltageRatioChangeListener {
x += it.voltageRatio * MOVEMENT_MULTIPLIER
}
open()
ended.listen { close() }
}
with (DigitalInput()) {
hubPort = 0
addStateChangeListener {
// Launch a coroutine in the program context
// to get back to OpenRNDR drawing thread
// since phidget event handlers are run in
// a different thread/context
launch {
drawer.isolatedWithTarget(rtCanvas) {
drawer.clear(ColorRGBa.BLACK)
}
}
}
open()
ended.listen { close() }
}
extend {
drawer.isolatedWithTarget(rtCanvas) {
drawer.stroke = ColorRGBa.WHITE
drawer.circle(x, y, 10.0)
}
drawer.image(rtCanvas.colorBuffer(0))
}
}
}
One thing of notere here is that when I clear the rtCanvas I have to do that within a launch { } block to get back to the main thread, this will fail otherwise. (It’s great that it’s so easy to get back to the drawing thread so easily by just using OPENRNDR’s implementation of launch.)
DigitalOutput
This can be used to control a light or a relay or something.
import com.phidget22.DigitalOutput
import org.openrndr.application
import org.openrndr.color.ColorRGBa
fun main() = application {
configure {
width = 768
height = 576
}
program {
// Storing the value and using apply now since
// we want to interact with the object later
// in order to control the output
val digitalOutput = DigitalOutput().apply {
channel = 0
open()
ended.listen { close() }
}
var outputActive = false
mouse.buttonUp.listen {
// Flip the state
digitalOutput.state = !digitalOutput.state
// Reflect changes in local variable
outputActive = digitalOutput.state
}
extend {
if (outputActive) {
drawer.clear(ColorRGBa.WHITE)
} else {
drawer.clear(ColorRGBa.BLACK)
}
}
}
}
++
I’ve found Phidgets very useful throughout the years and I’m looking forward to test them more with OPENRNDR. Hopefully this might be useful for someone else as well!
Some gotchas to be aware of
- Sometimes you need to set isHubPortDevice to true, other times not. I’m not sure when so I’d recommending trying out both if you have trouble connecting.
- Several of the methods like open() and others can throw exceptions. In the context of OPENRNDR I’ve totally ignored this as OPENRNDR’s default behaviour of exiting on exceptions and printing the exception with stack trace is very useful, but depending on what you’re doing you might want to catch them. This will not necessarily be the case when using Kotlin in another context.
- Setting the dataInterval superlow to something like 1 or 2 usually doesn’t work out that well.