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

Face Detection Using OpenCV With Raspberry Pi
Intermediate
  • 2,372
  • 21

Protip

In this project, we are going to detect faces using OpenCV with Raspberry Pi.

Creating an IoT Server with Home Assistant and MQTT
Intermediate
  • 2,523
  • 17

Full instructions

Home Assistant is one powerful home automation platform and it is complacently open source. Access it from any web browser and automatons.

Adding RC Transmitter/Receiver to Control Mecanum Bot
Intermediate
  • 431
  • 6

Full instructions

We previously showed our Mecanum Bot being controlled by a wireless Xbox controller, now we added RC transmitter/receiver.

Automated GPS Controlled Photo Taker
Intermediate
  • 1,613
  • 19

Full instructions

For this build, I put together a backpack to take pictures when I am close to places that like. And what better place to test than London?

Setting up a "User Data" drive for Raspberry Pi
Intermediate
  • 34
  • 1

Full instructions

Mounting external memory to store users programs, files, etc...

DIY Smart Assistant Speaker/Lamp (Google Home or Alexa)
Intermediate
  • 1,644
  • 5

Full instructions

DIY build using 3D printed enclosure and parts, a recycled Bluetooth speaker, a Raspberry Pi, and a Philips Hue light bulb.

ProjectsCommunitiesTopicsContestsLiveAppsBetaFree StoreBlogAdd projectSign up / Login