Home .Net PowerMate programming in .Net – Part 2
formats

PowerMate programming in .Net – Part 2


This is post number 2 about creating .Net software for the Griffin PowerMate. If you haven’t read post 1 yet, now is a good time :-) .

My goal is to use PowerMate in .Net, use C#, and if possible, loose the dependency of the Windows Driver Development Kit.

Some useful links I came across:

  • The HID page - Excellent USB and HID resource
  • Mike O’Brien’s Hid Library for .Net’
  • HDIAPI - multiplatform HDI access (Windows, Linux, OSX)
  • Busdog for inspecting USB traffic. See this MSDN article for how to enable unsigned drivers on Windows 7 x64. My laptop wasn’t stable with regards to hibernation when running this driver, so I uninstalled it as soon as I’d gotten what I wanted from it.

As the first link shows, controlling the PowerMate from .Net has been done before, but I’d rather create a solution that was pure C# and that didn’t rely on the DDK, which is a hassle to install and set up.

After looking around some more, I came across Mike O’Brien‘s excellent Hid Library for .Net. The library can be downloaded as a zip archive from github or cloned from the command line using git. It builds out of the box, without any external dependencies. The Hid Library wraps the relevant parts of the underlying Win32 API, which is

  • Event and file I/O from kernel32.dll
  • Device notifications from user32.dll
  • SetupDI API from setupapi.dll
  • Hid api from hid.dll, for HID device enumeration, configuration, capability information and some I/O

The library comes with a couple of examples for a gamepad, a card reader and a scanner. The examples typically adds their own data structures on top of the Hid Library, for instance to treat an array of byte of data from a Hid report as an array of bits and map it to a series of bool properties on a .Net class, reflecting buttons pressed on a gamepad.

All the information needed to do the same for the PowerMate can be found in the Hid protocol specification for the PowerMate (which received by email from Griffin after making a request), and in the SDK sample code (also available from Griffin on request).

The first thing you need to do is to open the Hid device itself. To be able to do that, you need is the vendor ID and the product ID for the PowerMate. For some reason, this information was not included in the PowerMate Hid protocol specification, but the SDK sample code reveals the following:

enum {
	kVendorID  = 0x077d,
	kProductID  = 0x0410,
	kVersionNumber = 0x0311
};

Getting a list of PowerMate devices from the Hid library is just this one line of code:

IEnumerable powerMates = HidDevices.Enumerate(VendorId, ProductId);

This list will include all PowerMates currently connected to the computer. If no PowerMates are connected, or if the PowerMate device driver hasn’t been installed, the list would be empty.

I only have one PowerMate, so getting the first device can be done like this:

HidDevice device = HidDevices.Enumerate(VendorId, ProductId).FirstOrDefault();

After getting the device handle, the next step is opening the device.

device.OpenDevice();

To get data from the PowerMate, you call the ReadReport method on the device, specifying a delegate method that is called when the data has been received. The Hid Library internally sets up multiple threads for reading data from Hid devices, so you need to be aware that the delegate might be called from a different thread than the thread calling ReadReport in the first place.

device.ReadReport(OnReport);

void OnReport(HidReport report)
{
    // parse report.Data
}

If you’re using WindowsForms and want to do the processing in the GUI thread, do something like this:

void OnReport(HidReport report)
{
    if (InvokeRequired)
    {   // Event can be received on a separate thread, so we need to push the message
        // back on the GUI thread before we execute.
        BeginInvoke(new Action(OnReport), report);
        return;
    }
    // parse report.Data and update the GUI
}

If you’re using WPF and want to synchronize the threads, use the Dispatcher class instead.

The Data property of the HidReport instance is just a byte array. The HidLibrary won’t help you parse this byte array. This is where the PowerMate Hid protocol specification is needed. According to the specification, the report will contain at least 6 bytes, laid out as follows:

Byte 0: Button
Byte 1: Knob displacement
Byte 2: 0
Byte 3: LED brightness
Byte 4: LED status (pulsing or constant brightness, pulse while sleep)
Byte 5: LED multiplier (for pulsing LED)

With a little help from the PowerMate SDK sample for the LED status byte and multiplier byte, I ended up with the following way of parsing the byte array:

enum PowerMateButtonState
{
    Up=0, Down=1
}

PowerMateButtonState buttonState = (data[0] == 0) ?
    PowerMateButtonState.Up : PowerMateButtonState.Down;
int knobDisplacement = data[1] < 128 ? data[1] : -256 + data[1];
int ledBrightness = data[2];
bool ledPulseEnabled = (data[4] & 0x01) == 0x01;
bool ledPulseDuringSleepEnabled = (data[4] & 0x04) == 0x04;
int ledPulseSpeedFlags = (data[4] & 0x30) >> 4;
int ledPulseSpeed = 0;

switch (ledPulseSpeedFlags) {
    case 0: ledPulseSpeed = -data[5]; break;
    case 1: ledPulseSpeed = 0; break;
    case 2: ledPulseSpeed = data[5]; break;
}

The knobDisplacement is zero when the knob is not being rotated and the value does normally lie very close to zero, typically +1 for slow clockwise movements and -1 for slow counter clockwise movements.

The ledPulseSpeed is 0 for the default pulse speed (approximately 0.5 Hz) and positive for faster speed, negative for slower speed.

To close the device, you just call

CloseDevice();

And that’s basically all you need to connect to and read data from the PowerMate.

To directly control the LED brightness, you can use the WriteReport method of the HidDevice class. Like this:

byte[] data = new byte[2];
data[0] = 0;
data[1] = (byte)brightness;
HidReport report = new HidReport(2, new HidDeviceData(data,
    HidDeviceData.ReadStatus.Success));
device.WriteReport(report);

The PowerMate Hid specification and the Powermate SDK shows that it is possible to get more control over the LED. It can be set up to pulse with verying speed, and it can be set up to start pulsing when the computer goes into sleep mode. This is however not possible through the WriteReport method, as this data is Hid feature data, not Hid report data. The HidLibrary didn’t have support for writing feature data, so I added a WriteFeatureData() method and a CreateFeatureOutputBuffer() method to make this possible.

public bool WriteFeatureData(byte[] data)
{
    if (_deviceCapabilities.FeatureReportByteLength         return false;

    var buffer = CreateFeatureOutputBuffer();
    Array.Copy(data, 0, buffer, 0, Math.Min(data.Length,
        _deviceCapabilities.FeatureReportByteLength));

    IntPtr hidHandle = IntPtr.Zero;
    bool success = false;

    try
    {
        hidHandle = OpenDeviceIO(_devicePath, NativeMethods.ACCESS_NONE);
        var overlapped = new NativeOverlapped();
        success = NativeMethods.HidD_SetFeature(hidHandle, buffer, buffer.Length);
    }
    catch (Exception exception)
    {
        throw new Exception(string.Format("Error accessing HID device '{0}'.",
            _devicePath), exception);
    }
    finally
    {
        if (hidHandle != IntPtr.Zero)
            CloseDeviceIO(hidHandle);
    }

    return success;
}

private byte[] CreateFeatureOutputBuffer()
{
    return CreateBuffer(Capabilities.FeatureReportByteLength - 1);
}

I’ve collected the code for reading and parsing messages from the PowerMate, for setting the LED brightness, and for encoding and writing messages to the PowerMate in three small classes.

The PowerMateState class holds the state of the PowerMate:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GriffinPowerMate
{
    /// <summary>
    /// State for the PowerMate button - Up or Down
    /// </summary>
    public enum PowerMateButtonState
    {
        Up=0, Down=1
    }

    /// <summary>
    /// Holds the state for the PowerMate: button state, knob displacement,
    /// led brightness and pulse settings.
    ///
    /// The struct is immutable.
    /// </summary>
    public struct PowerMateState
    {
        private PowerMateButtonState buttonState; // byte 0
        private int knobDisplacement; // byte 1
        private int ledBrightness; // byte 3
        private bool ledPulseEnabled; // byte 4, bit 1 set
        private bool ledPulseDuringSleepEnabled; // byte 4, bit 3 set
        private int ledPulseSpeed; // byte 5
        private bool valid;

        /// <summary>
        /// Initializes a new instance (valid) of the PowerMate class.
        /// </summary>
        /// <param name="buttonState"></param>
        /// <param name="knobDisplacement"></param>
        /// <param name="ledBrightness"></param>
        /// <param name="ledPulseEnabled"></param>
        /// <param name="ledPulseDuringSleepEnabled"></param>
        /// <param name="ledPulseSpeed"></param>
        public PowerMateState(PowerMateButtonState buttonState,
                              int knobDisplacement,
                              int ledBrightness,
                              bool ledPulseEnabled,
                              bool ledPulseDuringSleepEnabled,
                              int ledPulseSpeed)
        {
            this.buttonState = buttonState;
            this.knobDisplacement = knobDisplacement;
            this.ledBrightness = ledBrightness;
            this.ledPulseEnabled = ledPulseEnabled;
            this.ledPulseDuringSleepEnabled = ledPulseDuringSleepEnabled;
            this.ledPulseSpeed = ledPulseSpeed;
            this.valid = true;
        }

        /// <summary>
        /// Gets the PowerMate's button state.
        /// </summary>
        public PowerMateButtonState ButtonState
        {
            get { return buttonState; }
        }

        /// <summary>
        /// Gets the PowerMate's knob displacement. The valid range is
        /// [-127, +128], although most values are single digit values close to
        /// zero. A positive value indicates a clockwise rotation.
        /// </summary>
        public int KnobDisplacement
        {
            get { return knobDisplacement; }
        }

        /// <summary>
        /// Gets the PowerMate's LED brightness. The valid range is [0, 255],
        /// with 0 being completely off and 255 being full brightness.
        /// </summary>
        public int LedBrightness
        {
            get { return ledBrightness; }
        }

        /// <summary>
        /// Gets a value indicating if the PowerMate's LED is pulsing or constant
        /// brightness. Returns True if the LED is pulsing, and False if the LED
        /// has constant brightness.
        /// </summary>
        public bool LedPulseEnabled
        {
            get { return ledPulseEnabled; }
        }

        /// <summary>
        /// Gets a value indicating if the PowerMate's LED will pulse when the
        /// computer is in sleep mode.
        /// </summary>
        public bool LedPulseDuringSleepEnabled
        {
            get { return ledPulseDuringSleepEnabled; }
        }

        /// <summary>
        /// Gets the PowerMate's LED pulse speed. The range is  [-255, 255],
        /// and the useful range seems to be approximately [-32, 64]. A value of
        /// 0 means default pulse speed. A negative value is slower than the
        /// default, a positive value is faster.
        /// </summary>
        public int LedPulseSpeed
        {
            get { return ledPulseSpeed; }
        }

        /// <summary>
        /// Gets a value indicating in the PowerMateState instance is valid.
        /// </summary>
        public bool IsValid
        {
            get { return valid; }
        }
    }
}

The PowerMateEventArgs class just wraps the state in a EventArgs class, so it can be used with events:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GriffinPowerMate
{
    /// <summary>
    /// Provides data for PowerMate events.
    /// </summary>
    public class PowerMateEventArgs : EventArgs
    {
        /// <summary>
        /// Initializes a new instance of the PowerMateEventArgs class.
        /// </summary>
        /// <param name="state"></param>
        public PowerMateEventArgs(PowerMateState state)
        {
            State = state;
        }

        /// <summary>
        /// Gets the current PowerMate state.
        /// </summary>
        public PowerMateState State { get; private set; }
    }
}

The PowerMateManager class is where most of the action is. It opens and closes the PowerMate device with the correct vendor and product ID, sets and gets LED pulse mode (constant or pulsing, pulse during sleep), LED pulse speed and LED brightness (when not pulsing).

The PowerMateManager class also creates events when a device is attached or removes and when the PowerMate state changes:

  • DeviceAttached
  • DeviceRemoved
  • ButtonDown
  • ButtonUp
  • KnobDisplacement
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using HidLibrary;

namespace GriffinPowerMate
{
    /// <summary>
    /// Manages one PowerMate device.
    /// </summary>
    public class PowerMateManager : IDisposable
    {
        private const int VendorId = 0x077d;
        private const int ProductId = 0x0410;
        private HidDevice device;
        private bool attached = false;
        private bool connectedToDriver = false;
        private PowerMateState prevState;
        private bool debugPrintRawMessages = false;
        private bool disposed = false;

        /// <summary>
        /// Occurs when a PowerMate device is attached.
        /// </summary>
        public event EventHandler DeviceAttached;

        /// <summary>
        /// Occurs when a PowerMate device is removed.
        /// </summary>
        public event EventHandler DeviceRemoved;

        /// <summary>
        /// Occurs when the PowerMate button changes state from Up to Down.
        /// </summary>
        public event EventHandler<PowerMateEventArgs> ButtonDown;

        /// <summary>
        /// Occurs when the PowerMate button changes state from Down to Up.
        /// </summary>
        public event EventHandler<PowerMateEventArgs> ButtonUp;

        /// <summary>
        /// Occurs when the PowerMate is rotated.
        /// </summary>
        public event EventHandler<PowerMateEventArgs> KnobDisplacement;

        /// <summary>
        /// Initializes a new instance of the PowerMateManager class.
        /// </summary>
        public PowerMateManager()
        {
            prevState = new PowerMateState(PowerMateButtonState.Up, 0, 0, false,
                false, 0);
        }

        /// <summary>
        /// Attempts to connect to a PowerMate device.
        ///
        /// After a successful connection, a DeviceAttached event will normally
        /// be sent.
        /// </summary>
        /// <returns>True if a PowerMate device is connected,
        ///          False otherwise.</returns>
        public bool OpenDevice()
        {
            device = HidDevices.Enumerate(VendorId, ProductId).FirstOrDefault();

            if (device != null)
            {
                connectedToDriver = true;
                device.OpenDevice();

                device.Inserted += DeviceAttachedHandler;
                device.Removed += DeviceRemovedHandler;

                device.MonitorDeviceEvents = true;

                device.ReadReport(OnReport); 

                return true;
            }

            return false;
        }

        /// <summary>
        /// Closes the connection to the device.
        ///
        /// FIXME: Verify that this also shuts down any thread waiting for device
        /// data. 2012-06-07 thammer.
        /// </summary>
        public void CloseDevice()
        {
            device.CloseDevice();
            connectedToDriver = false;
        }

        /// <summary>
        /// Closes the connection to the device.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Sends a message to the PowerMate device to enable pulsing of the LED.
        /// The (constant) LED brightness will be ignored. The pulse speed is set
        /// by the SetLedPulseBrightness() method.
        /// </summary>
        /// <param name="enable">True to enable pulsing, False otherwise.</param>
        public void SetLedPulseEnabled(bool enable)
        {
            if (connectedToDriver)
            {
                byte [] data = new byte[9];
                data[0] = 0x00;
                data[1] = 0x41;
                data[2] = 0x01;
                data[3] = 0x03; // command
                data[4] = 0x00;
                data[5] = enable ? (byte)0x01 : (byte)0x00;
                data[6] = 0x00;
                data[7] = 0x00;
                data[8] = 0x00;
                HidReport report = new HidReport(9, new HidDeviceData(data,
                    HidDeviceData.ReadStatus.Success));
                device.WriteFeatureData(data);
            }
        }

        /// <summary>
        /// Sends a message to the PowerMate device to enable pulsing of the LED
        /// while the computer is sleeping.
        /// </summary>
        /// <param name="enable">True to enable pulsing during sleep, False
        ///                      otherwse.</param>
        public void SetLedPulseDuringSleepEnabled(bool enable)
        {
            if (connectedToDriver)
            {
                byte[] data = new byte[9];
                data[0] = 0x00;
                data[1] = 0x41;
                data[2] = 0x01;
                data[3] = 0x02;
                data[4] = 0x00; // command
                data[5] = enable ? (byte)0x01 : (byte)0x00;
                data[6] = 0x00;
                data[7] = 0x00;
                data[8] = 0x00;
                HidReport report = new HidReport(9, new HidDeviceData(data,
                    HidDeviceData.ReadStatus.Success));
                device.WriteFeatureData(data);
            }
        }

        /// <summary>
        /// Sets the PowerMate's LED pulse speed. The range is  [-255, 255], and
        /// the useful range seems to be approximately [-32, 64]. A value of 0
        /// means default pulse speed. A negative value is slower than the
        /// default, a positive value is faster.
        /// </summary>
        /// <param name="speed"></param>
        public void SetLedPulseSpeed(int speed)
        {
            if (connectedToDriver)
            {
                byte[] data = new byte[9];
                data[0] = 0x00;
                data[1] = 0x41;
                data[2] = 0x01;
                data[3] = 0x04; // command
                data[4] = 0x00; // Table 0
                if (speed < 0)
                {
                    data[5] = 0;
                    data[6] = (byte)(-speed);
                }
                else if (speed == 0)
                {
                    data[5] = 1;
                    data[6] = 0;
                }
                else // speed > 0
                {
                    data[5] = 2;
                    data[6] = (byte)(speed);
                }
                data[7] = 0x00;
                data[8] = 0x00;
                HidReport report = new HidReport(9, new HidDeviceData(data,
                    HidDeviceData.ReadStatus.Success));
                device.WriteFeatureData(data);
            }
        }

        /// <summary>
        /// Sets the PowerMate's LED brightness.
        /// </summary>
        /// <param name="brightness">The brightness of the LED. The valid range
        /// is [0, 255], with 0 being completely off and 255 being full brightness.
        /// </param>
        public void SetLedBrightness(int brightness)
        {
            if (connectedToDriver)
            {
                byte[] data = new byte[2];
                data[0] = 0;
                data[1] = (byte)brightness;
                HidReport report = new HidReport(2, new HidDeviceData(data,
                    HidDeviceData.ReadStatus.Success));
                device.WriteReport(report);
            }
        }

        private void OnButtonDown(PowerMateState state)
        {
            var handle = ButtonDown;
            if (handle != null)
            {
                handle(this, new PowerMateEventArgs(state));
            }
        }

        private void OnButtonUp(PowerMateState state)
        {
            var handle = ButtonUp;
            if (handle != null)
            {
                handle(this, new PowerMateEventArgs(state));
            }
        }

        private void OnKnobDisplacement(PowerMateState state)
        {
            var handle = KnobDisplacement;
            if (handle != null)
            {
                handle(this, new PowerMateEventArgs(state));
            }
        }

        private void GenerateEvents(PowerMateState state)
        {
            if (state.ButtonState == PowerMateButtonState.Down &&
                prevState.ButtonState == PowerMateButtonState.Up)
            {
                OnButtonDown(state);
            }
            else if (state.ButtonState == PowerMateButtonState.Up &&
                prevState.ButtonState == PowerMateButtonState.Down)
            {
                OnButtonUp(state);
            }

            if (state.KnobDisplacement != 0)
            {
                OnKnobDisplacement(state);
            }

            prevState = state;
        }

        private void DeviceAttachedHandler()
        {
            attached = true;

            if (DeviceAttached != null)
                DeviceAttached(this, EventArgs.Empty);

            device.ReadReport(OnReport);
        }

        private void DeviceRemovedHandler()
        {
            attached = false;

            if (DeviceRemoved != null)
                DeviceRemoved(this, EventArgs.Empty);
        }

        private void OnReport(HidReport report)
        {
            if (attached == false) { return; }

            if (report.Data.Length >= 6)
            {
                PowerMateState state = ParseState(report.Data);
                if (!state.IsValid)
                {
                    System.Diagnostics.Debug.WriteLine("Invalid PowerMate state");
                }
                else
                {
                    GenerateEvents(state);

                    if (debugPrintRawMessages)
                    {
                        System.Diagnostics.Debug.Write("PowerMate raw data: ");
                        for (int i = 0; i < report.Data.Length; i++)
                        {
                            System.Diagnostics.Debug.Write(String.Format("{0:000} ",
                                report.Data[i]));
                        }
                        System.Diagnostics.Debug.WriteLine("");
                    }
                }
            }

            device.ReadReport(OnReport);
        }

        private PowerMateState ParseState(byte[] data)
        {
            if (data.Length >= 6)
            {
                PowerMateButtonState buttonState = (data[0] == 0) ?
                    PowerMateButtonState.Up : PowerMateButtonState.Down;
                int knobDisplacement = data[1] < 128 ? data[1] : -256 + data[1];
                int ledBrightness = data[2];
                bool ledPulseEnabled = (data[4] & 0x01) == 0x01;
                bool ledPulseDuringSleepEnabled = (data[4] & 0x04) == 0x04;
                int ledPulseSpeedFlags = (data[4] & 0x30) >> 4;
                int ledPulseSpeed = 0;
                switch (ledPulseSpeedFlags) {
                    case 0: ledPulseSpeed = -data[5]; break;
                    case 1: ledPulseSpeed = 0; break;
                    case 2: ledPulseSpeed = data[5]; break;
                }

                return new PowerMateState(buttonState, knobDisplacement,
                    ledBrightness, ledPulseEnabled, ledPulseDuringSleepEnabled,
                    ledPulseSpeed);
            }
            else
            {
                return new PowerMateState(); // Invalid state
            }
        }

        /// <summary>
        /// Closes any connected devices.
        /// </summary>
        /// <param name="disposing"></param>
        private void Dispose(bool disposing)
        {
            if(!this.disposed)
            {
                if(disposing)
                {
                    CloseDevice();
                }

                disposed = true;
            }
        }

        /// <summary>
        /// Destroys instance and frees device resources (if not freed already)
        /// </summary>
        ~PowerMateManager()
        {
            Dispose(false);
        }

    }
}

Mike has included my support for writing feature data as well as some example code for the PowerMate in his HidLibrary.

The classes are included in a Visual Studio project called “PowerMate” in the “examples/GriffinPowerMate” directory. The project builds a small assembly that you include in your application, together with the HidLibrary assembly.

I have created two example projects that uses this assembly, both of which can be found in the “examples/GriffinPowerMate” directory. One example builds a command line application and another builds a WindowsForms application. The “GriffinPowerMate.sln” solution in the same directory includes the two examples, as well as the PowerMate assembly itself.

The console example opens a PowerMate device, if a device can be found, and prints messages whenever the button is pressed, the know is rotated, or the device is removed or attached again.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GriffinPowerMate
{
    /// <summary>
    /// Checks to see if a Griffin PowerMate device is connected. If it is,
    /// read rotation and button press events and control the LED brightness
    /// and pulse speed.
    ///
    /// This code has not been tested with multiple PowerMates connected at the
    /// same time.
    /// </summary>
    class Program
    {
        static PowerMateManager powerMateManager = new PowerMateManager();

        static void Main(string[] args)
        {
            if (powerMateManager.OpenDevice())
            {
                powerMateManager.DeviceAttached += powerMate_DeviceAttached;
                powerMateManager.DeviceRemoved += powerMate_DeviceRemoved;
                powerMateManager.ButtonDown += powerMate_ButtonDown;
                powerMateManager.ButtonUp += powerMate_ButtonUp;
                powerMateManager.KnobDisplacement += powerMate_KnobDisplacement;

                Console.WriteLine("PowerMate found, press any key to exit.");
                Console.ReadKey();
                powerMateManager.CloseDevice();
            }
            else
            {
                Console.WriteLine("Could not find a PowerMate.");
                Console.ReadKey();
            }
        }

        static void powerMate_DeviceRemoved(object sender, EventArgs e)
        {
            Console.WriteLine("PowerMate removed.");
        }

        static void powerMate_DeviceAttached(object sender, EventArgs e)
        {
            Console.WriteLine("PowerMate attached.");
        }

        static void powerMate_KnobDisplacement(object sender, PowerMateEventArgs e)
        {
            Console.WriteLine("PowerMate knob displacement event");
            Console.WriteLine("PowerMate state button: {0}",
                (e.State.ButtonState == PowerMateButtonState.Up ? "Up" : "Down"));
            Console.WriteLine("PowerMate state knob displacement: {0}",
                e.State.KnobDisplacement);
        }

        static void powerMate_ButtonUp(object sender, PowerMateEventArgs e)
        {
            Console.WriteLine("PowerMate button up event");
            Console.WriteLine("PowerMate state button: {0}",
                (e.State.ButtonState == PowerMateButtonState.Up ? "Up" : "Down"));
            Console.WriteLine("PowerMate state knob displacement: {0}",
                e.State.KnobDisplacement);
        }

        static void powerMate_ButtonDown(object sender, PowerMateEventArgs e)
        {
            Console.WriteLine("PowerMate button down event");
            Console.WriteLine("PowerMate state button: {0}",
                (e.State.ButtonState == PowerMateButtonState.Up ? "Up" : "Down"));
            Console.WriteLine("PowerMate state knob displacement: {0}",
                e.State.KnobDisplacement);
        }
    }
}

A typical run is shown in the screenshot below:

PowerMate Console Example

The GUI example uses most of the features in the PowerMateManager class. The user can click the PowerMate button to switch LED pulse on and off, rotate the know to set LED brightness (when pulsing is off) or set LED pulse speed (when pulsing is on). The application also displays the current connection status, button state, last rotation increment, LED brightness, LED pulse mode and LED pulse speed.

PowerMate Gui Example

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using GriffinPowerMate;

namespace GriffinPowerMateWindowsForms
{
    /// <summary>
    /// Displays a window indicating the current state of the PowerMate and
    /// allows the user to control the LED.
    ///
    /// The window will indicate:
    /// - if a PowerMate device is connected
    /// - the PowerMate button state
    /// - the last PowerMate rotation direction and magnitude
    /// - the PowerMate LED brightness (constant, when not pulsing)
    /// - if pulsing of the PowerMate is enabled
    /// - the LED pulse speed
    ///
    /// The user can control the PowerMate LED brightness
    /// by rotating the PowerMate when the LED is not pulsing.
    ///
    /// LED pulsing is started and stopped by clicking the PowerMate button.
    ///
    /// The user can control the PowerMate LED pulse speed
    /// by rotating the PowerMate when the LED is pulsing.
    /// </summary>
    public partial class PowerMateViewer : Form
    {
        public PowerMateViewer()
        {
            InitializeComponent();

            connectionTimer.Interval = 500;
            connectionTimer.Tick += new EventHandler(connectionTimer_Tick);
            connectionTimer.Start();
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed;
        ///                         otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                powerMateManager.CloseDevice();
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void connectionTimer_Tick(object sender, EventArgs e)
        {
            bool connected = ConnectToPowerMate();
            if (connected)
            {
                connectionTimer.Stop();
                statusLabel.Text = "Connected";
            }
        }

        private bool ConnectToPowerMate()
        {
            if (powerMateManager.OpenDevice())
            {
                powerMateManager.DeviceAttached += powerMate_DeviceAttached;
                powerMateManager.DeviceRemoved += powerMate_DeviceRemoved;
                powerMateManager.ButtonDown += powerMate_ButtonDown;
                powerMateManager.ButtonUp += powerMate_ButtonUp;
                powerMateManager.KnobDisplacement += powerMate_KnobDisplacement;

                System.Diagnostics.Debug.WriteLine("PowerMate found");
                return true;
            }
            else
            {
                System.Diagnostics.Debug.WriteLine("Could not find a PowerMate.");
                return false;
            }
        }

        private void powerMate_DeviceRemoved(object sender, EventArgs e)
        {
            if (InvokeRequired)
            {   // Event can be received on a separate thread, so we need to
                // push the message back on the GUI thread before we execute.
                BeginInvoke(new Action<object, EventArgs>(powerMate_DeviceRemoved),
                    sender, e);
                return;
            }

            statusLabel.Text = "Disconnected";
            System.Diagnostics.Debug.WriteLine("PowerMate removed.");
        }

        private void powerMate_DeviceAttached(object sender, EventArgs e)
        {
            if (InvokeRequired)
            {   // Event can be received on a separate thread, so we need to
                // push the message back on the GUI thread before we execute.
                BeginInvoke(new Action<object, EventArgs>(powerMate_DeviceAttached),
                    sender, e);
                return;
            }

            statusLabel.Text = "Connected";
            System.Diagnostics.Debug.WriteLine("PowerMate attached.");
        }

        private void powerMate_KnobDisplacement(object sender,
            PowerMateEventArgs e)
        {
            if (InvokeRequired)
            {   // Event can be received on a separate thread, so we need to
                // push the message back on the GUI thread before we execute.
                BeginInvoke(new Action<object,
                    PowerMateEventArgs>(powerMate_KnobDisplacement), sender, e);
                return;
            }

            UpdateGUIFromState(e.State);

            if (!e.State.LedPulseEnabled)
            {   // Set LED brightness
                int range = rotationProgressBar.Maximum -
                    rotationProgressBar.Minimum + 1;
                int value = (rotationProgressBar.Value + e.State.KnobDisplacement -
                    rotationProgressBar.Minimum) % range;
                if (value < 0)
                    value = rotationProgressBar.Maximum + value + 1;
                else
                    value = rotationProgressBar.Minimum + value;
                rotationProgressBar.Value = value;

                int brightness = value * 255 / range;
                powerMateManager.SetLedBrightness(brightness);
                ledBrightnessLabel.Text = brightness.ToString();

            }
            else
            {   // Set pulse speed
                int speed = e.State.LedPulseSpeed + e.State.KnobDisplacement;
                speed = speed < -32 ? -32 : speed > 32 ? 32 : speed;
                powerMateManager.SetLedPulseSpeed(speed);
                ledPulseSpeedLabel.Text = speed.ToString();
            }
        }

        private void powerMate_ButtonUp(object sender, PowerMateEventArgs e)
        {
            if (InvokeRequired)
            {   // Event can be received on a separate thread, so we need to
                // push the message back on the GUI thread before we execute.
                BeginInvoke(new Action<object, PowerMateEventArgs>
                    (powerMate_ButtonUp), sender, e);
                return;
            }

            UpdateGUIFromState(e.State);
            ledPulseLabel.Text = !e.State.LedPulseEnabled ? "On" : "Off";

            powerMateManager.SetLedPulseEnabled(!e.State.LedPulseEnabled);
        }

        private void powerMate_ButtonDown(object sender, PowerMateEventArgs e)
        {
            if (InvokeRequired)
            {   // Event can be received on a separate thread, so we need to
                // push the message back on the GUI thread before we execute.
                BeginInvoke(new Action<object, PowerMateEventArgs>
                    (powerMate_ButtonDown), sender, e);
                return;
            }

            UpdateGUIFromState(e.State);
        }

        private void UpdateGUIFromState(PowerMateState state)
        {
            buttonLabel.Text = state.ButtonState == PowerMateButtonState.Up ?
                "Up" : "Down";
            rotationLabel.Text = state.KnobDisplacement.ToString();
            ledBrightnessLabel.Text = state.LedBrightness.ToString();
            ledPulseLabel.Text = state.LedPulseEnabled ? "On" : "Off";
            ledPulseSpeedLabel.Text = state.LedPulseSpeed.ToString();
        }

        private PowerMateManager powerMateManager = new PowerMateManager();
        private Timer connectionTimer = new Timer();
    }
}

I have created an installer with the two example applications and the PowerMate and HidLibrary assembly, which can be downloaded from here.

The complete source code is included in Mike O’Brien’s HidLibrary. All source code is licensed under the MIT License.

Good luck developing software for the PowerMate. Feel free to post your results as comments below if you create something cool.

 
 Share on Facebook Share on Twitter Share on Reddit Share on LinkedIn
4 Comments  comments 

4 comments on “PowerMate programming in .Net – Part 2

  1. Nick on said:

    I have been tryin to do something similar to this but using Max/MSP. The trouble I have is that the PowerMate is not being recognised in Max as a HID. The workaround I used was using the keystrokes functionality and converting that to MIDI – but the downfall was that the Max window had to be active in order for this to work.

    http://www.youtube.com/watch?v=utebwVZvCQI

    Most of the above is far too technical for me but I wondered if there was anyway a creating a driver for the PowerMate that enabled it as a standard HI device?

    Nick
    http://www.myredhotcar.co.uk

    • Thomas Hammer on said:

      Hi Nick.

      I’m not sure why the PowerMate doesn’t show up in Max/MSP, as it is a HID device. Perhaps only class-compliant devices are supported?

      Regards,
      Thomas

  2. Ben9091243 on said:

    Any luck getting all this to work with multiple PowerMates at a time?

    • Thomas Hammer on said:

      Hi.

      I’ve recently gotten a second PowerMate and I’m reworking the code to support multiple devices. I’ll post an update (with code) when I’m done.

      Thomas

Leave a Reply

Your email address will not be published. Required fields are marked *

*

HTML tags are not allowed.