Windows 10, the UWP, Sphero 2 and Raspberry PI 2–Continued

Following on from that previous post, I wanted to see if I could gain some control over my Sphero device without having to plug a mouse into the Raspberry PI and control the UI.

I had a search through my Sunfounder kit of components and came up with this interesting looking gadget which is a Rotary Encoder with a switch built into it;

It serves 2 purposes – the pole in the middle can be rotated and the device can report the direction of that rotation. That pole can also be depressed to act as a switch.

I figured that this would give me enough to at least rotate the Raspberry PI and provide some kind of speed control and so I went with it.

In terms of connecting it to my Raspberry PI 2, I largely followed what I saw in this YouTube video;

That is – the device has 5 pins on it and I wired them as below;

  • GND – wired to ground!
  • + wired to 3V3.
  • CLK wired to GPIO6.
  • DT wired to GPIO5.
  • SW wired to GPIO22 but also ‘pulled high’ by putting a 10K ohm resistor between it an 5V.
    • It’s possible that I should also have pull-up resistors on the CLK/DT pins but I seem to ‘get away’ without them.
    • I didn’t really know that I needed to do this until finding that, without the resistor, the value on this pin would just ‘float’ high/low all the time and give me no sensible readings. I’m not an electronics type person so I had to ask and watch a YouTube video before I figured this out.

That’s pretty much it. With that in place, I wrote this class to represent this RotaryEncoderWithSwitch;

  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using Windows.Devices.Gpio;
  using Windows.UI.Xaml;
  using Windows.UI.Xaml.Controls;

  enum RotaryEncoderTurnDirection
  {
    Clockwise,
    Anticlockwise
  }
  class RotaryEncoderTurnEventArgs
  {
    public RotaryEncoderTurnEventArgs(RotaryEncoderTurnDirection direction)
    {
      this.Direction = direction;
    }
    public RotaryEncoderTurnDirection Direction { get; }
  }
  class RotaryEncoderWithSwitch
  {
    public event EventHandler<RotaryEncoderTurnEventArgs> Turn;
    public event EventHandler SwitchPressed;

    public RotaryEncoderWithSwitch(int aDtPin, int bClkPin, int switchPin)
    {
      this.aDtPin = aDtPin;
      this.bClkPin = bClkPin;
      this.switchPin = switchPin;
      this.direction = RotaryEncoderTurnDirection.Clockwise;

      this.pins = new Dictionary<int, GpioPin>();

      foreach (var pin in new int[] { this.aDtPin, this.bClkPin, this.switchPin })
      {
        GpioPin gpioPin = GpioController.GetDefault().OpenPin(pin);
        gpioPin.ValueChanged += OnValueChanged;
        this.pins[pin] = gpioPin;

        if (pin == this.switchPin)
        {
          gpioPin.Write(GpioPinValue.High);
        }
        gpioPin.SetDriveMode(GpioPinDriveMode.Input);

      }
    }
    void OnValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
    {
      if (sender == this.pins[this.switchPin])
      {
        if (args.Edge == GpioPinEdge.FallingEdge)
        {
          var handlers = this.SwitchPressed;
          if (handlers != null)
          {
            handlers(this, EventArgs.Empty);
          }
        }
      }
      else
      {
        var aPinValue = this.pins[this.aDtPin].Read();
        var bPinValue = this.pins[this.bClkPin].Read();

        var highCount =
          ((aPinValue == GpioPinValue.High) ? 1 : 0) +
          ((bPinValue == GpioPinValue.High) ? 1 : 0);

        if (highCount == (previousHighCount + 1))
        {
          if (previousHighCount == 0)
          {
            this.direction = aPinValue == GpioPinValue.High ?
              RotaryEncoderTurnDirection.Anticlockwise : RotaryEncoderTurnDirection.Clockwise;
          }
          previousHighCount = highCount;
        }
        else if ((previousHighCount == 2) && (highCount == 1))
        {
          // we're on the falling edge. we flag this as a rotation.
          var handlers = this.Turn;

          if (handlers != null)
          {
            handlers(this, new RotaryEncoderTurnEventArgs(this.direction));
          }
          previousHighCount = 0;
        }
        else
        {
          // start over, accepting that we might miss some.
          previousHighCount = 0;
        }
      }
    }
    Dictionary<int, GpioPin> pins;
    int aDtPin;
    int bClkPin;
    int switchPin;
    int previousHighCount;
    RotaryEncoderTurnDirection direction;
  }

I’m fairly certain that this isn’t quite perfect but it seemed to work out in as much as I needed it to. With that in place, I was ready to try and retro-fit this into the code that I’d written in the previous blog post.

Making use of classes like GpioController means reaching out to the Windows IoT Extensions for the UWP and so I added that reference to my project and then I modified the view model class that I’d previously written to ‘drive’ the Sphero in order to incorporate the use of this class on devices where the APIs exist.

namespace BigWhiteBall.ViewModels
{
  using System;
  using BigWhiteBall.Services;
  using Utility;
  using Windows.System.Profile;
  using System.Threading;

  class DriveSpheroControlViewModel : ViewModelBase
  {
    public DriveSpheroControlViewModel(ISpheroService spheroService)
    {
      this.spheroService = spheroService;
      this.context = SynchronizationContext.Current;

      // New.
      if (this.IsIoTFamily)
      {
        this.rotaryEncoder = new RotaryEncoderWithSwitch(
          A_PIN_DT, B_PIN_CLK, SW_PIN);

        this.rotaryEncoder.Turn += OnGpioTurnInput;
        this.rotaryEncoder.SwitchPressed += OnGpioSwitchPressed;
      }
    }
    public float SpeedValue
    {
      get
      {
        return (this.speedValue);
      }
      set
      {
        base.SetProperty(ref this.speedValue, value);
        this.DriveSphero();
      }
    }
    public double RotationValue
    {
      get
      {
        return (this.rotationValue);
      }
      set
      {
        base.SetProperty(ref this.rotationValue, value);
        this.RotateSphero();
      }
    }
    void DriveSphero()
    {
      this.spheroService.Drive(this.speedValue / MAX_SLIDER_VALUE);
    }
    void RotateSphero()
    {
      this.spheroService.Rotate((int)this.rotationValue);
    }
    /// <summary>
    /// New
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void OnGpioSwitchPressed(object sender, EventArgs e)
    {
      this.context.Post(
        _ =>
        {
          var newSpeed = this.SpeedValue + SPEED_INCREMENT;

          // We have two speeds - half way and stopped!
          if (newSpeed >= MAX_SLIDER_VALUE)
          {
            newSpeed = 0.0f;
          }
          this.SpeedValue = newSpeed;
        },
        null
      );
    }
    /// <summary>
    /// New
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void OnGpioTurnInput(object sender, RotaryEncoderTurnEventArgs e)
    {
      this.context.Post(
        _ =>
        {
          var delta =
            (e.Direction == RotaryEncoderTurnDirection.Clockwise) ?
            ANGLE_ROTATION : 0 - ANGLE_ROTATION;

          var newAngle = this.RotationValue + delta;

          if ((newAngle > 360.0d) || (newAngle <= 0.0d))
          {
            newAngle = 0.0d;
          }
          this.RotationValue = newAngle;
        },
        null);
    }
    /// <summary>
    /// New
    /// </summary>
    bool IsIoTFamily
    {
      get
      {
        // NB. I can use contract detection and type detection and method detection
        // but the problem is that the Windows.Devices.DevicesLowLevelContract says
        // that it is available on the PC and the GpioController type says that
        // it is available on the PC but when you try and get the default controller
        // the method throws and so doing all that contract detection becomes a
        // waste of time in that case.
        return (
          AnalyticsInfo.VersionInfo.DeviceFamily == IOT_FAMILY);
      }
    }
    float speedValue;
    static readonly float MAX_SLIDER_VALUE = 100.0f;
    double rotationValue;
    ISpheroService spheroService;

    // New: Added the RotaryEncoderWithSwitch for IoT platforms.
    RotaryEncoderWithSwitch rotaryEncoder;

    // New 
    SynchronizationContext context;

    // New: Added these hard coded pin values.
    static readonly int A_PIN_DT = 5;
    static readonly int B_PIN_CLK = 6;
    static readonly int SW_PIN = 22;

    // New
    static readonly string IOT_FAMILY = "Windows.IoT";

    // New
    static readonly double ANGLE_ROTATION = 20.0d;
    static readonly int SPEED_INCREMENT = 50;
  }
}

and then I took it for a test drive Smile My driving wasn’t very god and especially not while holding the camera;

and that’s perhaps all there is to say about that for the moment – the code’s here for download if you want to do something with it.