Patricia
Published © CC BY

Exploration Vehicle (Powered by Azure)

My first steps on the way to the Star Gate exploration robot backed up by Azure infrastructure.

IntermediateFull instructions provided6,361
Exploration Vehicle (Powered by Azure)

Things used in this project

Hardware components

Raspberry Pi 3 Model B
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

Microsoft Azure
Microsoft Azure
IoT Hub Free Tier is sufficient for development
Windows 10 IoT Core
Microsoft Windows 10 IoT Core
Visual Studio 2015
Microsoft Visual Studio 2015

Hand tools and fabrication machines

patience

Story

Read more

Code

PanTiltServo

C#
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();
            });
        }
    }
}

Wheels

C#
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);
        }
    }
}

FakeWheels

C#
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();
            }
        }
    }
}

ObstacleSensors

C#
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 };
        }
    }
}

Main

C#
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

Patricia

Patricia

5 projects • 18 followers
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.

Comments