Things used in this project

Hardware components:
Pi 3 02
Raspberry Pi 3 Model B
Pi 2 is also going to be sufficient, you only need a wifi module for communication with star gate center infrastructure ;-)
×1
4tronix initio robot kit
Supersonic Kit is availible on ebay.
×1
Microsoft Lifecam 3000 USB Camera
×1
Software apps and online services:
W9gt7hzo
Microsoft Azure
IoT Hub Free Tier is sufficient for development
10
Microsoft Windows 10 IoT Core
Vs2015logo
Microsoft Visual Studio 2015
Hand tools and fabrication machines:
patience

Code

PanTiltServoC#
Pan and tilts servo using PWM
using Microsoft.IoT.Lightning.Providers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices;
using Windows.Devices.Gpio;
using Windows.Devices.Pwm;
using Windows.Foundation;

namespace SimpleController.Domain
{
    internal class PanTiltServo
    {
        private PwmPin mLeftPanServoPwm;
        private PwmController mPwmController;
        private PwmPin mBottomPanServo;
        private PwmPin mTopTiltServo;
        private PwmController pwmControlleTilt;

        public PanTiltServo()
        {
            Task.Run(async () =>
            {
                await init();
            }).Wait();
        }

        private async Task init()
        {
            try
            {
                var pwmControllers = await PwmController.GetControllersAsync(LightningPwmProvider.GetPwmProvider());
                var pwmControllerPan = pwmControllers[1];
                pwmControllerPan.SetDesiredFrequency(350);
                mPwmController = pwmControllerPan;

                pwmControllers = await PwmController.GetControllersAsync(LightningPwmProvider.GetPwmProvider());
                pwmControlleTilt = pwmControllers[1];
                pwmControlleTilt.SetDesiredFrequency(450);

                mTopTiltServo = pwmControlleTilt.OpenPin(24);
                mBottomPanServo = mPwmController.OpenPin(25);
                
                mTopTiltServo.SetActiveDutyCyclePercentage(0.5);
                mBottomPanServo.SetActiveDutyCyclePercentage(0.5);

                mTopTiltServo.Start();
                mBottomPanServo.Start();
                
                Task.Run(() =>
                {
                    System.Threading.Tasks.Task.Delay(250).Wait();
                    mBottomPanServo.Stop();
                    mTopTiltServo.Stop();
                });
            }
            catch (Exception ex)
            {
                throw;
            }
        }
        
        public void TogglePanRight()
        {
            mBottomPanServo.SetActiveDutyCyclePercentage(Math.Max(mBottomPanServo.GetActiveDutyCyclePercentage() - 0.01, 0));
            mBottomPanServo.Start();
            Task.Run(() =>
            {
                System.Threading.Tasks.Task.Delay(100).Wait();
                mBottomPanServo.Stop();
            });
        }
        public void TogglePanLeft()
        {
            mBottomPanServo.SetActiveDutyCyclePercentage(Math.Min(mBottomPanServo.GetActiveDutyCyclePercentage() + 0.01, 1));
            mBottomPanServo.Start();
            Task.Run(() =>
            {
                System.Threading.Tasks.Task.Delay(100).Wait();
                mBottomPanServo.Stop();
            });
        }

        public void ToggleTiltUp()
        {
            mTopTiltServo.SetActiveDutyCyclePercentage(Math.Max(mBottomPanServo.GetActiveDutyCyclePercentage() - 0.01, 0));
            mTopTiltServo.Start();
            Task.Run(() =>
            {
                System.Threading.Tasks.Task.Delay(100).Wait();
                mTopTiltServo.Stop();
            });
        }

        public void ToggleTiltDown()
        {
            mTopTiltServo.SetActiveDutyCyclePercentage(Math.Min(mBottomPanServo.GetActiveDutyCyclePercentage() + 0.01, 1));
            mTopTiltServo.Start();
            Task.Run(() =>
            {
                System.Threading.Tasks.Task.Delay(100).Wait();
                mTopTiltServo.Stop();
            });
        }
    }
}
WheelsC#
The Wheels, this is really straight forward. At a later time. i will test what happens if i use pwm on the Motors.
using Windows.Devices.Gpio;

namespace SimpleController.Domain
{
    internal class Wheels : IWheels
    {
        private GpioController mGpioController;
        private GpioPin mLeftMotorForward;
        private GpioPin mLeftMotorBackwards;
        private GpioPin mRightMotorForward;
        private GpioPin mRightMotorBackwards;

        public void Init()
        {
            mGpioController = GpioController.GetDefault();

            mLeftMotorForward = mGpioController.OpenPin(16);
            mLeftMotorForward.Write(GpioPinValue.Low);
            mLeftMotorForward.SetDriveMode(GpioPinDriveMode.Output);

            mLeftMotorBackwards = mGpioController.OpenPin(19);
            mLeftMotorBackwards.Write(GpioPinValue.Low);
            mLeftMotorBackwards.SetDriveMode(GpioPinDriveMode.Output);

            mRightMotorForward = mGpioController.OpenPin(13);
            mRightMotorForward.Write(GpioPinValue.Low);
            mRightMotorForward.SetDriveMode(GpioPinDriveMode.Output);

            mRightMotorBackwards = mGpioController.OpenPin(12);
            mRightMotorBackwards.Write(GpioPinValue.Low);
            mRightMotorBackwards.SetDriveMode(GpioPinDriveMode.Output);
        }

        public bool IsMovingForward
        {
            get
            {
                return mRightMotorForward.Read() == GpioPinValue.High && mLeftMotorForward.Read() == GpioPinValue.High;
            }
        }

        public void MoveForward()
        {
            mRightMotorBackwards.Write(GpioPinValue.Low);
            mLeftMotorBackwards.Write(GpioPinValue.Low);
            mRightMotorForward.Write(GpioPinValue.High);
            mLeftMotorForward.Write(GpioPinValue.High);
        }

        public void MoveBackwards()
        {
            mRightMotorForward.Write(GpioPinValue.Low);
            mLeftMotorForward.Write(GpioPinValue.Low);
            mRightMotorBackwards.Write(GpioPinValue.High);
            mLeftMotorBackwards.Write(GpioPinValue.High);
        }

        public void Stop()
        {
            mRightMotorForward.Write(GpioPinValue.Low);
            mLeftMotorForward.Write(GpioPinValue.Low);
            mRightMotorBackwards.Write(GpioPinValue.Low);
            mLeftMotorBackwards.Write(GpioPinValue.Low);
        }

        public void TurnRight()
        {
            mRightMotorForward.Write(GpioPinValue.Low);
            mLeftMotorBackwards.Write(GpioPinValue.Low);
            mRightMotorBackwards.Write(GpioPinValue.High);
            mLeftMotorForward.Write(GpioPinValue.High);
        }

        public void TurnLeft()
        {
            mLeftMotorForward.Write(GpioPinValue.Low);
            mRightMotorBackwards.Write(GpioPinValue.Low);
            mRightMotorForward.Write(GpioPinValue.High);
            mLeftMotorBackwards.Write(GpioPinValue.High);
        }
    }
}
FakeWheelsC#
For debugging purposes of the Remote Control Application, i created a fake wheels implementation that send data to the IoT Hub.
using Microsoft.Azure.Devices.Client;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SimpleController.Domain
{
    internal class FakeWheels : IWheels
    {
        private static DeviceClient mDeviceClient;
        
        private static Queue<string> pendingMessagesQueue = new Queue<string>();

        public bool IsMovingForward
        {
            get
            {
                return false;
            }
        }

        public FakeWheels()
        {
            mDeviceClient = DeviceClient.Create(App.IOTHUB_URI, new DeviceAuthenticationWithRegistrySymmetricKey(MainPage.GetUniqueDeviceId(), App.DEVICE_KEY));

            Task.Run(() =>
            {
                sendDeviceToCloudMessagesAsync();
            });
        }

        public void Init()
        {
        }

        public void MoveBackwards()
        {
            pendingMessagesQueue.Enqueue("moving backwards");
        }

        public void MoveForward()
        {
            pendingMessagesQueue.Enqueue("moving forward");
        }
        public void Stop()
        {
            pendingMessagesQueue.Enqueue("stoping");
        }

        public void TurnLeft()
        {
            pendingMessagesQueue.Enqueue("turning right");
        }

        public void TurnRight()
        {
            pendingMessagesQueue.Enqueue("turning left");
        }

        private static async void sendDeviceToCloudMessagesAsync()
        {
            while (true)
            {
                while (pendingMessagesQueue.Any())
                {
                    try
                    {
                        var messagepart = pendingMessagesQueue.Peek();

                        var data = new
                        {
                            deviceId = MainPage.GetUniqueDeviceId(),
                            action = messagepart
                        };

                        var messageString = JsonConvert.SerializeObject(data);
                        var message = new Message(Encoding.ASCII.GetBytes(messageString));
                        await mDeviceClient.SendEventAsync(message);

                        pendingMessagesQueue.Dequeue();
                    }
                    catch (Exception) { }
                }

                Task.Delay(1000).Wait();
            }
        }
    }
}
ObstacleSensorsC#
Class for the obstacle sensors triggers a event each time the GPIO reading changes.
using System;
using Windows.Devices.Gpio;

namespace SimpleController.Domain
{
    public class ObstacleSensors
    {
        public class SensorReading : EventArgs
        {
            public GpioPinValue LeftValue { get; set; }
            public GpioPinValue RightValue { get; set; }

            public override bool Equals(object obj)
            {
                var other = obj as SensorReading;
                if (other == null)
                {
                    return false;
                }

                return LeftValue == other.LeftValue && RightValue == other.RightValue;
            }
        }

        public delegate void ValueReadingChangedEventHandler(object sender, SensorReading e);
        public event ValueReadingChangedEventHandler ValueReadingChanged;

        private GpioController mGpioController;
        private GpioPin mLeftSensor;
        private GpioPin mRightSensor;

        public ObstacleSensors()
        {
            mGpioController = GpioController.GetDefault();
            mRightSensor = mGpioController.OpenPin(17);
            mRightSensor.SetDriveMode(GpioPinDriveMode.Input);
            mRightSensor.ValueChanged += MRightSensor_ValueChanged;
            mLeftSensor = mGpioController.OpenPin(4);
            mLeftSensor.SetDriveMode(GpioPinDriveMode.Input);
            mLeftSensor.ValueChanged += MLeftSensor_ValueChanged;
        }

        private void MRightSensor_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
        {
            ReadCurrentValues();
        }

        private void MLeftSensor_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
        {
            ReadCurrentValues();
        }

        private SensorReading mLastReading = null;

        public SensorReading LastReading
        {
            get
            {
                return mLastReading;
            }
            set
            {
                if (value != null)
                {
                    if (!value.Equals(LastReading))
                    {
                        if (ValueReadingChanged != null)
                        {
                            var del = ValueReadingChanged;
                            del(this, value);
                        }
                    }

                    mLastReading = value;
                }
            }
        }

        public SensorReading ReadCurrentValues()
        {
            var valueLeft = mLeftSensor.Read();
            var valueRight = mRightSensor.Read();
            return LastReading = new SensorReading { LeftValue = valueLeft, RightValue = valueRight };
        }
    }
}
MainC#
The main page that is currently handling events from keyboard and iot hub. Also performing some instrumentation between different parts.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using SimpleController.Domain;
using Windows.Devices.Gpio;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.System;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using System.Threading.Tasks;
using Windows.System.Profile;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;
using Windows.Security.Cryptography;
using Windows.Networking.Connectivity;
using Microsoft.Azure.Devices.Client;
using Newtonsoft.Json;
using System.Text;
using Windows.Foundation.Metadata;
using Microsoft.IoT.Lightning.Providers;
using Windows.Devices;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace SimpleController
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        private IWheels mWheels;
        private DeviceClient mDeviceClient;
        private ObstacleSensors mObstacleSensors;
        private PanTiltServo mPanTiltServo;
        private Camera mCamera;

        public MainPage()
        {
            if (LightningProvider.IsLightningEnabled)
            {
                LowLevelDevicesController.DefaultProvider = LightningProvider.GetAggregateProvider();
            }

            this.InitializeComponent();
            mDeviceClient = DeviceClient.Create(App.IOTHUB_URI, new DeviceAuthenticationWithRegistrySymmetricKey(GetUniqueDeviceId(), App.DEVICE_KEY), TransportType.Amqp);
            registerKeyEvents();

            var hasGpio = GpioController.GetDefault() != null;
            if (hasGpio)
            {
                mWheels = new Wheels();
                mObstacleSensors = new ObstacleSensors();
                mObstacleSensors.ValueReadingChanged += MObstacleSensors_ValueReading;
                mPanTiltServo = new PanTiltServo();
            }
            else
            {
                mWheels = new FakeWheels();
                mPanTiltServo = new PanTiltServo();
            }

            mCamera = new Camera();
            mCamera.Init();
            mWheels.Init();
            NetworkInformation.NetworkStatusChanged += NetworkInformation_NetworkStatusChanged;
            Task.Run(() =>
            {
                receiveC2dAsync();
            });
        }

        private async void MObstacleSensors_ValueReading(object sender, ObstacleSensors.SensorReading e)
        {
            try
            {
                if (e.LeftValue == GpioPinValue.High && e.RightValue == GpioPinValue.High)
                {
                    if (mWheels.IsMovingForward)
                    {
                        mWheels.Stop();
                    }
                }
                var data = new
                {
                    deviceId = MainPage.GetUniqueDeviceId(),
                    source = "Obstacle Sensor",
                    leftValue = e.LeftValue,
                    rightValue = e.RightValue,
                };

                var messageString = JsonConvert.SerializeObject(data);
                var message = new Message(Encoding.ASCII.GetBytes(messageString));
                await mDeviceClient.SendEventAsync(message);
            }
            catch (Exception ex)
            {
            }
        }

        private void NetworkInformation_NetworkStatusChanged(object sender)
        {
            var internetprofile = NetworkInformation.GetInternetConnectionProfile();
            if (internetprofile == null)
            {
                mWheels.Stop();
            }
        }

        private void registerKeyEvents()
        {
            //http://stackoverflow.com/questions/32781864/get-keyboard-state-in-universal-windows-apps
            Window.Current.CoreWindow.KeyDown += (s, e) =>
            {
                if (e.VirtualKey == VirtualKey.W)
                {
                    mWheels.MoveForward();
                }
                if (e.VirtualKey == VirtualKey.S)
                {
                    mWheels.Stop();
                }
                if (e.VirtualKey == VirtualKey.X)
                {
                    mWheels.MoveBackwards();
                }
                if (e.VirtualKey == VirtualKey.A)
                {
                    mWheels.TurnLeft();
                }
                if (e.VirtualKey == VirtualKey.D)
                {
                    mWheels.TurnRight();
                }
                if (e.VirtualKey == VirtualKey.R)
                {
                    mObstacleSensors?.ReadCurrentValues();
                }
                if (e.VirtualKey == VirtualKey.T)
                {
                    mPanTiltServo?.TiltUp();
                }
                if (e.VirtualKey == VirtualKey.G)
                {
                    mPanTiltServo?.TiltUp();
                }
                if (e.VirtualKey == VirtualKey.V)
                {
                    mPanTiltServo?.PanLeft();
                }
                if (e.VirtualKey == VirtualKey.B)
                {
                    mPanTiltServo?.PanRight();
                }
                if (e.VirtualKey == VirtualKey.M)
                {
                    mCamera?.StartCapture();
                }
                if (e.VirtualKey == VirtualKey.N)
                {
                    mCamera?.StopCapture();
                }
            };
        }

        internal static string GetUniqueDeviceId()
        {
            var deviceInformation = new Windows.Security.ExchangeActiveSyncProvisioning.EasClientDeviceInformation();
            return deviceInformation.Id.ToString();
        }

        private async void receiveC2dAsync()
        {
            while (true)
            {

                try
                {
                    Message receivedMessage = await mDeviceClient.ReceiveAsync();
                    if (receivedMessage == null) continue;
                    if (receivedMessage.Properties["Created"] != null)
                    {
                        try
                        {
                            var rawvalue = receivedMessage.Properties["Created"];
                            var createdDateTime = new DateTime(long.Parse(rawvalue));
                            var now = DateTime.UtcNow;
                            var difference = now - createdDateTime;
                            if (difference > new TimeSpan(0, 0, 0, 0, 500))
                            {
                                await mDeviceClient.RejectAsync(receivedMessage);
                                continue;
                            }
                        }
                        catch (Exception)
                        {
                            await mDeviceClient.RejectAsync(receivedMessage);
                            continue;
                        }
                    }
                    if (receivedMessage.Properties["Action"] == "Forward")
                    {
                        mWheels.MoveForward();
                    }
                    if (receivedMessage.Properties["Action"] == "Backward")
                    {
                        mWheels.MoveBackwards();
                    }
                    if (receivedMessage.Properties["Action"] == "Stop")
                    {
                        mWheels.Stop();
                    }
                    if (receivedMessage.Properties["Action"] == "Left")
                    {
                        mWheels.TurnLeft();
                    }
                    if (receivedMessage.Properties["Action"] == "Right")
                    {
                        mWheels.TurnRight();
                    }
                    if (receivedMessage.Properties["Action"] == "SERVO_TILT")
                    {
                        var value = double.Parse(receivedMessage.Properties["Value"], System.Globalization.CultureInfo.InvariantCulture);
                        mPanTiltServo.TiltToPosition(value);
                    }
                    if (receivedMessage.Properties["Action"] == "SERVO_CENTER")
                    {
                        mPanTiltServo.Center();
                    }
                    if (receivedMessage.Properties["Action"] == "SERVO_PAN")
                    {
                        var value = double.Parse(receivedMessage.Properties["Value"], System.Globalization.CultureInfo.InvariantCulture);
                        mPanTiltServo.PanToPosition(value);
                    }
                    if (receivedMessage.Properties["Action"] == "FEED_START")
                    {
                        mCamera?.StartCapture();
                    }
                    if (receivedMessage.Properties["Action"] == "FEED_STOP")
                    {
                        mCamera?.StopCapture();
                    }
                    await mDeviceClient.CompleteAsync(receivedMessage);
                }
                catch (Exception ex)
                {
                }

            }
        }
    }
}
Full source code for the Pi Device and Remote Control
RoverController.sln contains project for the robot RoverControlCenter.sln, respectively for remote control. Some projects are obsolete and were left for reference.

Credits

944886 10207332723049051 1010476743701632688 n wxrlaregcv
Patricia

A c# developer. As child i used to play with electronics and this source of fun is living up again in me, as we enter the age of IoT.

Contact

Replications

Did you replicate this project? Share it!

I made one

Love this project? Think it could be improved? Tell us what you think!

Give feedback

Comments

Similar projects you might like

LoRaWAN for Raspberry Pi with Worldwide Frequency Support
Intermediate
  • 2,862
  • 12

Full instructions

LoRaWAN LMIC 1.6 for Raspberry Pi with Dragino LoRA/GPS HAT or standalone RFM95W LoRa Module.

Human-Following Robot with Kinect
Intermediate
  • 1,880
  • 20

Full instructions

Instead of using single camera and complicated image recognition algorithms we can take advantage of Kinect libraries.

Computerception
Intermediate
  • 27,142
  • 20

Full instructions

Why not put a Raspberry Pi into a 2007 netbook?

Raspberry Pi Google Assistant With Sleek Wood Box
Intermediate
  • 4,747
  • 26

Full instructions

l built a DIY Google AI Assistant using a Raspberry Pi, USB Speaker and USB microphone.

Automated Indoor Gardener
Intermediate
  • 3,693
  • 38

Full instructions

Never worry about dead plants again. This automated gardener never forgets to water your plants and provide artificial sunlight.

Smart Eye for Your Pi
Intermediate
  • 1,744
  • 10

Full instructions

Having some fun combining clarifai and Mycroft.ai.

Sign up / LoginProjectsPlatformsTopicsContestsLiveAppsBetaFree StoreBlog