Tutorial on using Phidgets for sensors/actuators++

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.

262E3CEE-93B2-4240-BFB9-DFE4800C8683

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!

015EEB80-3305-4ADE-9D9D-EADFA1421D96

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.

E31148C6-E756-45CE-9A11-74D41FE6B873

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.

569546E5-CA55-4102-822C-F3ED5CCA9383

Double clicking this should give you this window:

33E23FD2-6575-44ED-9640-47586506E528

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.)

0BEE05FC-F5C6-4837-B9D8-91454AD3A24E

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).

BFC38D70-98B5-433F-AB5F-43A6EDC70F18

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.
2 Likes

Thank you for the tutorial and the examples! I never heard about Phidgets before and only used Arduino or Raspberry Pi.

First thing I wondered was what about pricing, and I found a list of dealers in this page: Dealers - Phidgets Support Being in Europe, I recognized Robotshop which is where I got my Axidraw from.

Is it the case that Phidgets are more expensive than building everything using simple components yourself, but you save time debugging the hardware?

ps. What about moving this to the Tutorials section?

1 Like

I don’t have long experience using them, but I know people who’s made interactive installations for museums, etc swears by them. One of the main reasons given is that they are reliable in ways Arduino and Raspberry Pis just aren’t.

Personally I dislike to solder and fiddle with the hands-on stuff of electronics. Before I used Phidgets I used Seeeduino Grove System (which is kinda like SparkFun qwiic) for Arduino where you can just snap components to it very easily. However, I had to fiddle with getting information from the Arduino to the computer. Writing an Arduino program til send the data along or use Firmata.

Personally I find Phidgets just much easier to work with and prototype with (espescially in Kotlin), tho I’ve done most work with it in JavaScript. I like that it’s comes with proper API and documentation and feels much less like a hack.

I can’t comment specifically on whether it is lower cost in the long run, but I personally at least think they’re worth it.

Good suggesting moving it to Tutorials! I didn’t see the category when posting!